はじめに
少し前に、python でバイナリデータを読んでpacket をparse したり(PREAMBLEから読み始めてチェックサムで確認するとか)、データを読んだり(2byte を整数値にするとか)したりしました。そのとき、改めてバイナリデータの扱いを調べたので、メモ。何度も調べて、良く分かんらないまま使っていましたが、今は少しわかった気でいます。??
実際に仕事ではセンサーにコマンドを送ったり受け取ったメッセージを出コードするところを行うための実装でした。皆さんOSSを使うところを、おじさんはポチポチとコードを書いて読み解いています。
bytes型 (自分の理解)
pythonで定義されている bytes 型のデータを正しく使えば、何事もなく実装ができるはず、、です。
bytes型はどこからくるか
プログラム上で変数を作ることもできますが、自分の場合、ずばり、
- binary type でfileを読んでいるとき
- socket 通信しているとき
- serial ポートからデータを読んでいるとき
です。そのとき、read/write しているのはこのbytes 型です。
In [ ]: with open("hoge.bin","rb") as f:
...: buf = f.read(4)
...: print(type(buf))
...:
<class 'bytes'>
整数値のリスト <--> bytes型
bytes 型と「整数値のリスト」は異なるものです。相互には変換可能です。(もちろん整数値は0から255に限られますが)
整数値
0から255までの整数値が1 byte を表すのはご存じのとおりです。なので、0から255までの整数値の系列(list 型)をbytes型に相互に変換できるのは分かり易いです。int()
や bytes()
で cast するだけです。
In [15]: aaa = [1,2,3,4,5,255]
In [16]: bytes(aaa)
Out[16]: b'\x01\x02\x03\x04\x05\xff'
In [25]: list(bytes(aaa))
Out[25]: [1, 2, 3, 4, 5, 255]
ただし、整数値は0から255の間の整数値に限られます。
In []: aaa = [1,2,3,4,5,256]
In []: bytes(aaa)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-18-3b250851e608> in <module>
----> 1 bytes(aaa)
ValueError: bytes must be in range(0, 256)
16進数で表記
0xff と書くとint 型になります。bytes 型にするには b'\xff
と書きます。
bytes型の変数は、リストで参照するように [ ] でインデックスを指定して参照することができます。
In []: b1 = b'\xaabbcc'
In []: type(b1)
Out[]: bytes
In []: b1[0]
Out[]: 170
In []: hex(b1[0])
Out[]: '0xaa'
以下のように書いたら、変数で整数値になります。
In [15]: i1 = 0xaabbcc
In [16]: type(i1)
Out[16]: int
In [17]: i1
Out[17]: 11189196
当然、これを [ ] で参照することはできません。
In [18]: i1[0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-18-9726669fc462> in <module>
----> 1 i1[0]
TypeError: 'int' object is not subscriptable
これをbytes 型にするには、to_bytes で変換する必要があります。endian を指定します。
In [21]: i1.to_bytes(3, 'little')
Out[21]: b'\xcc\xbb\xaa'
In [22]: i1.to_bytes(3, 'big')
Out[22]: b'\xaa\xbb\xcc'
In [27]: i1.to_bytes(3,'big')[0]
Out[27]: 170
bytes 型とstr型
これはdecode/encode するだけです。文字コードは必要な時に指定します。
In [31]: "こんにちは".encode()
Out[31]: b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf'
In [32]: b2 = "こんにちは".encode()
In [33]: b2.decode()
Out[33]: 'こんにちは'
int型とstr型(2進数、16進数表記)
話は前後しますが、整数値は2進数や16進数表記の文字列に変換できます。
In [37]: hex(100)
Out[37]: '0x64'
In [38]: bin(100)
Out[38]: '0b1100100'
一方で、コードに直接書けばint 型になります。
In [39]: i3 = 0x64
In [40]: i3
Out[40]: 100
In [41]: i4 = 0b1100100
In [42]: i4
Out[42]: 100
import struct
そして、最後に、float を含めて連続していろいろつまっているbytes には unpack で全てdecode させてしまう、という、とても便利な関数unpack が用意されています。素直に、docs のexample を読むのが分かり易いです。
このunpack のおかげで、かなりコードの長さが短くなった気がします。
まとめ
bytes型で入ってくるデータは、適切な処理をすることで所望の読み方をすることができた。
あと少ししたら、どうせすぐ忘れてしまうので、メモしました。TODOは今のところ無しかな。
(2021/06/30)