今日も見に来てくださって、ありがとうございます。石川さんです。
ここのところ、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の書式文字列はエンディアン指定子と書式指定子の二種類あり、使うときはエンディアン指定子の方を書式指定子より先に記述します。
| 指定子 | バイト順 |
| < | リトルインディアン |
| > | ビッグエンディアン |
| 指定子 | 意味 | バイト数 |
| x | 1バイト読み飛ばし | 1 |
| c | char 長さ1のバイト列 | 1 |
| b | signed char 符号付整数 | 1 |
| B | unsigned char 符号なし整数 | 1 |
| h | short 符号付整数 | 2 |
| H | unsigned short 符号なし整数 | 2 |
| i | int 符号付整数 | 4 |
| I | unsigned int 符号なし整数 | 4 |
| l | long 符号付整数 | 4 |
| L | unsigned long 符号なし整数 | 4 |
| q | long long 符号付整数 | 8 |
| Q | unsigned long long 符号なし整数 | 8 |
| f | float 単精度浮動小数点数 | 4 |
| d | double 倍精度浮動小数点数 | 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データを変換できます。

