Pythonバイナリデータの扱いかた

 今日も見に来てくださって、ありがとうございます。石川さんです。

 ここのところ、Tkinterのユニットテストの実現に向けていろいろと調査を進めているのですが、若干難航しております。。。

 ということで、本ブログの更新が滞っていたのですが、更新がないよりも、何か自分にとって当たり前のことであっても誰かの役に立つかもしれないということで、Pythonの標準モジュールの範囲内でのバイナリデータについて書いてみたいと思います。

バイト

 Pythonでは、8ビットの整数を扱うために、bytesとbytearrayを用意してくれています。扱えるのは、8ビットで表現できる符号なし数値の範囲で、0~255です。

In [1]: bytes(5)
Out[1]: b'\x00\x00\x00\x00\x00'

In [2]: bytearray(5)
Out[2]: bytearray(b'\x00\x00\x00\x00\x00')

 上記のように、数値を指定すると0x00で初期化された指定されたサイズ(今回は5バイト)のバイトとバイト列を生成してくれます。以下のようにすると、リストの値から生成可能です。

In [3]: l = [1,2,3,4,5]

In [4]: b = bytes(l)

In [5]: b
Out[5]: b'\x01\x02\x03\x04\x05'

In [6]: ba = bytearray(l)

In [7]: ba
Out[7]: bytearray(b'\x01\x02\x03\x04\x05')

 ちなみに、bytesはイミュータブルでbytearrayはミュータブルです。

In [8]: b[2] = 0
Traceback (most recent call last):

  File "<ipython-input-107-088d74b352b8>", line 1, in <module>
    b[2] = 0

TypeError: 'bytes' object does not support item assignment

In [9]: b
Out[9]: b'\x01\x02\x03\x04\x05'

In [10]: ba[2] = 0

In [11]: ba
Out[11]: bytearray(b'\x01\x02\x00\x04\x05')

 ごらんの通り、bytesは変更できません。

エンディアン

 いきなり「エンディアン」というタイトルを書いてしまいましたが、ちょうどぼくが新入社員のときにはじめてであった概念で、あまりにも衝撃的だったので、よく覚えています。ま、実際には衝撃を受けるひとはそんなにいないと思いますけど。(笑)

 当時C言語を使っていました。整数値を格納するshort型は2バイト、long型は4バイト、int型は、処理系によって、2バイトだったり4バイトだったりする、というところまではなるほどー、という感じだったのですけど、実際に数値が格納された結果、上下のバイトが入れ替わる、という話です。まったくの謎でした。

 例えば、先ほどの2バイト分について考えてみます。1バイト目に0x01、2バイト目に0x02が入力されているので、2バイトが一つの数値だと考えると、0x0102ということです。2進数に置き換えて計算するとこの数値は、(0000000100000010)2となり、
2**8+2**1 = 256+2 = 258になると思いますよね。それが、上下のバイトが入れ替わると、
(0000001000000001)2となり、2**9+2**0 = 512+1 = 513ということです。ね、意味がわかりませんよね。

 では、先ほど生成したbytesをstructモジュールを使って読み込んでみます。まずは、サンプルをご覧ください。structはC言語の構造体に似たデータを処理するのに便利に使えます。

In [12]: import struct 

In [13]: b[:2]
Out[13]: b'\x01\x02'

In [14]: struct.unpack('>H',b[:2])
Out[14]: (258,)

In [15]: struct.unpack('<H',b[:2])
Out[15]: (513,)

 ここで、'<H’、’>H’を指定していますが、最初の記号はエンディアン指定子ということで、「>」こちらがビッグエンディアン、「<」こちらがリトルエンディアンです。結果をご覧の通り、ビッグエンディアンは、直感的に正しい気がする方で、リトルエンディアンが上下のバイトが入れ替わるヤツですね。ちなみに二番目の記号「H」は2バイトの符号なし単整数を扱う書式指定子ということになります。

 実際にこのエンディアンという言葉、ぼくにはあんまりなじみがなくて、いつもビッグとリトル、どっちだったっけ?と、なってしまいます。重要なのは、時と場合によって入れ替わる可能性がある、ということを知っておくことと、バイトを扱うときにはどちらが採用されているのか、ということに気を付けなければいけない、ということですね。

structの書式文字列

 structの書式文字列はエンディアン指定子と書式指定子の二種類あり、使うときはエンディアン指定子の方を書式指定子より先に記述します。

指定子バイト順
<リトルインディアン
>ビッグエンディアン
エンディアン指定子
指定子意味バイト数
x1バイト読み飛ばし1
cchar 長さ1のバイト列1
bsigned char 符号付整数1
Bunsigned char 符号なし整数1
hshort 符号付整数2
Hunsigned short 符号なし整数2
iint 符号付整数4
Iunsigned int 符号なし整数4
llong 符号付整数4
Lunsigned long 符号なし整数4
qlong long 符号付整数8
Qunsigned long long 符号なし整数8
ffloat 単精度浮動小数点数4
ddouble 倍精度浮動小数点数8
書式指定子

 バイトをPythonデータに変換するときは、unpackを利用しますが、その逆のときは、packを利用します。

In [16]: struct.pack('>2L',500,12)
Out[16]: b'\x00\x00\x01\xf4\x00\x00\x00\x0c'

In [17]: struct.unpack('>2L',b'\x00\x00\x01\xf4\x00\x00\x00\x0c')
Out[17]: (500, 12)

 この書式指定子は、ビッグエンディアンで、2個の4バイトの符号なし整数を扱う、ということを意味しています。ちなみに、「x」の書式指定子は、指定されたバイト数分を読み飛ばす、ということを意味しています。

In [18]: struct.unpack('>2xH2xH',b'\x00\x00\x01\xf4\x00\x00\x00\x0c')
Out[18]: (500, 12)

 2バイト飛ばして2バイトの符号なし整数、2バイト飛ばして2バイトの符号なし整数、ということを意味しています。

まとめ

 バイトを扱うときは、エンディアンに気を付けて。簡単なバイトなら、structを使って、バイトとPythonデータを変換できます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


reCAPTCHAの認証期間が終了しました。ページを再読み込みしてください。