2016/10/12

[py]python3は16進数の扱いが違うんだ

お仕事で、たまにpythonを触ることがある。
大したことをするわけではなく、ライブラリと一緒に配布されているexamplesに、ちょっと手を加えて実行する程度で、本格的にアプリを組むというのはやったことがない。

自覚しているが、私はどうもスクリプト言語が苦手だ。
そのせいか、pythonも便利そうなことはわかるのだけど、本気で覚える気力がわいていない。
これから使う機会が多いので、イヤイヤ期は卒業せねばならぬのだが。。。

 

さて、pythonがどうだ、というわけではないのだけど、16進数の扱いが難しいと感じる。
私がやると、どうしてもプロトコル解析とか、バイナリデータ操作とかが主なので、16進数で1バイトずつアクセスしたいのだ。

先週、ようやく

foo = '123456789abcdef'.decode('hex')
print foo.encode('hex')

という形でそれっぽく扱うことができることがわかった。
これでもう怖くない!と思っていたのだが、昨日使ったexamplesでは「decode()なんてないよ」といきなり怒られてしまった。

しばらく気付かなかったのだが、ファイルの先頭に「python3」などと書かれている。
gcc3とかgcc4とかと同じようなものかと思ったが、そうではなく、言語のバージョン自体が違うらしい。
そういえば、python2系とpython3系でどうのこうの、という記述を見たことがある。。。

どうも、python3では「bytes」という型があるらしい。
python3のbytes型とstr型の比較と変換方法 | Python Snippets

文字列の前に「b」と付けると、bytes型になるようだ。
16進数をバイト列にすると、こういう感じでよさそうだ。

foo = bytes.fromhex('123456789abcdef0')

printすると「\x」がついて邪魔なのだが、文字列じゃないからそのまま出力されても困るし、仕方ないところか。
変換すればよいだけなので、よしとしよう。

ただ、bytes型だけじゃなく、整数型としても16進数を扱えるので、「あれ、自分は16進数のデータをどうやって扱っているんだっけ?」ということになってしまう。
ライブラリに頼っているので、結局は受け入れてくれる形に変換するしかないのだ。

 

朝の続き。
bytesの16進数を、文字列で表示させたい。
C言語で言えば、こういう感じだ。

const uint8_t foo[] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 };
for (int lp = 0; lp < sizeof(foo); lp++) {
    printf("%02x", foo[lp]);
}
printf("\n");

python3だと、こうかな?

foo = bytes.fromhex('123456789abcdef0')
import binascii
print(binascii.hexlify(foo))

hexlify()は、b2a_hex()でもよいらしい。

これは悪くないのだが、

b'123456789abcdef0'

と、「b」とかシングルクオーテーションが付いてしまうのが気にくわない。
文字列だろうから、と「[2:]」などとしても、b'は消えない。
これは、hexilify()の戻り値がbytesだからだ。
つまり、16進数文字列を、16進数としてbytesに変換するだけだから、print()の方がb'を付けていることになる。

 

ちなみに、

foo = b'123456789abcdef0'

でやると、

b'31323334353637383961626364656630'

となる。
つまり、文字列を16進数にしているのだ。
C言語で言えば、文字列中にエスケープシーケンス\xで16進数を直接書いた場合に使うことになろう。

 

数値に変換したい場合は、C言語だとこう書くかな。

long val = strtol("abcdef", NULL, 16);

確か、文字列が0xで始まっていても、うまいことやってくれたような気がする。

print(int('123456789abcdef0', 16))

でやると、

1311768467463790320

となった。
「1311768467463790320 = 0x123456789ABCDEF0」だから、そのままの見た目で変換してくれるようだ。

 

であれば、さっきの話に戻るが、

print('%x' % int('123456789abcdef0', 16))

とすると、

123456789abcdef0

となるので、まだるっこしいが「b」やらシングルクオーテーションやらは取り除かれる。
目的は達するけど、ちょっとねぇ。。。。

 

こんなことすると、

print(('%x' % int('123456789abcdef0', 16)).zfill(20))

結果は、

0000123456789abcdef0

とゼロ埋めしてくれるので、悪くない。。。のかな。。。

 

ただ、数値で扱える範囲を超してしまうとダメだろうから、やはり1要素ずつ変換するのが素直なのではなかろうか。

foo = bytes.fromhex('123456789abcdef0')
print(''.join(format(x, '02x') for x in foo))

だと、

123456789abcdef0

なので、私が思っているイメージになった。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。