python-bitstring のドキュメントはすげー良くできてるからまとめ要らねんじゃね?

Python には bitstring という素敵 package があります。
GitHub - scott-griffiths/bitstring: A Python module to help you manage your bits
マニュアルもあるしこれまたすげー丁寧です。
GitHub - scott-griffiths/bitstring: A Python module to help you manage your bits
でも、加齢と共に学習能力が低下してるので書いて覚えるメソッド発動。Part I を適当に読み流すぜ!

>>> import bitstring

Chapter 3 Creation

bitstring って何かっつーと、bit 列を格納したり操作したりする為の package です。bit 列を格納してるクラスやインスタンスを作る関数が用意されています。

クラスが 4 つ用意されてるんですが、ベースになってるのが ConstBitArray でてんこ盛りにしたのが BitStream です。

pos 無し pos 有り
変更不可能 ConstBitArray ConstBitStream
変更可能 BitArray BitStream
  • Const* だと __hash__ があるんで dict の key になれる
  • filename など指定してファイルを読みこむ場合は Const* は最初からはファイル読みこまないけど Bit* の方は最初に全部ファイルを読みこもうとする

などの違いもあります。

bitstring の作り方

基本な作り方は 2 通りあって、引数で指定するか文字列で指定するか:

>>> bitstring.ConstBitArray(int=37, length=7)
ConstBitArray('0b0100101')
>>> bitstring.ConstBitArray('int:7=37')
ConstBitArray('0b0100101')

どっちも signed int の 37 を表現する長さが 7 の bit 列が作られます。で、文字列だと並べたりもできて尚のこと素敵です。

>>> bitstring.ConstBitArray('int:4=1, int:4=2')
ConstBitArray('0x12')

試しに int から作りましたが、他にもいろいろなものから作れます。manual から引用しちゃう:

auto これがデフォルトの動作。string をよきに計らってくれます。'0x00' とか上の 'int:7=37' とか。'0x', '0o', '0b' ではじまるとそれぞれ 16 進, 8 進, 2 進
bytes Python2 なら str, Python3 なら bytes をそのまま
hex, oct, bin '0xabc’, '0o123', '0b010101' などを解釈
int, uint int と unsigned int を。bit length が必要。bit-wise big-endian
intle, uintle byte-wise little-endian。なんで length は 8 の倍数。
intbe, uintbe byte-wise big-endian。なんで length は 8 の倍数。
intne, uintne byte-wise native-endian。なんで length は 8 の倍数。
float / floatbe, floatle, floatne 浮動小数endian 指定で、length は 32 or 64
se, ue Signed or unsigned exponential-Golomb coded integers、整数の encoding の一つね
bool True or False
filename path を指定してファイルを読む

exponential-Golomb code に関しては付録が用意されてるので興味がある人はマニュアルを読もう!

つーわけで 37 はこんな感じで bitstring になります:

>>> bitstring.ConstBitArray(uintle=37, length=16)
ConstBitArray('0x2500')
>>> bitstring.ConstBitArray(uintbe=37, length=16)
ConstBitArray('0x0025')
>>> bitstring.ConstBitArray(ue=37)
ConstBitArray('0b00000100110')
>>> bitstring.ConstBitArray(float=37, length=32)
ConstBitArray('0x42140000')

あとこの auto で解釈してくれるような文字列フォーマット (auto initialiser) は他の関数につっこんだりして、find でそれを検索したり == で比較するのにわざわざインスタンス作らなくても文字列との比較っぽく済ませられたり、中々重宝するのでしっかりマスターしましょう。

>>> a = bitstring.ConstBitArray('0b0101010101010101')
>>> a.find('0b0')
(0,)
>>> a.find('0b1')
(1,)
>>> i = a.findall('0b1')
>>> i.next()
1
>>> i.next()
3
>>> i.next()
5
>>> a=bitstring.ConstBitArray('0xabc')
>>> a.oct
'0o5274'
>>> a == '0o5274'
True
>>> b = bitstring.ConstBitArray('0b010101')
>>> b == 'int:6=21'
True

便利!!

Chapter 4 Packing

これが真骨頂。

struct.pack の強化版みたいなのに bitstring.pack があって、format と材料を与えるとそこから BitStream を作ってくれます。

>>> bitstring.pack('int:7=10, bool=True')
BitStream('0x15')
>>> bitstring.pack('int:7, bool', 10, True)
BitStream('0x15')
>>> bitstring.pack('int:m=10, bool=n', m=7, n=True)
BitStream('0x15')

なんか、すげー例がしょぼいですが

  • format で完全に指定も
  • format 用意しといて対応する実値を入れることも
  • format で length や値を keyword にしといて後から keyword に値入れることも

できてすげー柔軟です。かっこいい。

struct.pack が好きな人は

>>> bitstring.pack('>5H', 1, 10, 100, 1000, 10000)
BitStream('0x0001000a006403e82710')

なんてことできると知ると喜ぶに違いありません。

Chapter 5 Interpreting Bitstrings

bitstring は作ってしまったが最後単なる bit の列になります。単なる bit の列なんで、

>>> a=bitstring.ConstBitArray(bytes='abcd')
>>> a
ConstBitArray('0x61626364')
>>> a.int
1633837924L
>>> a.intle
1684234849
>>> a.float
2.6100787562286154e+20
>>> a.floatle
1.6777999408082104e+22

好きなように解釈できます。

似たようなのをもう 1 つ:

>>> a=bitstring.ConstBitArray(bytes='abcdef')
>>> a.bin
'0b011000010110001001100011011001000110010101100110'
>>> a.oct
'0o3026114331062546'
>>> a.hex
'0x616263646566'
>>> a.bytes
'abcdef'

float は 32-bits or 64-bits でないといけなかったり、oct は (3 の倍数)-bits、hex は (4 の倍数)-bits でないと解釈できないのでエラーが出ます。

InterpretError: Cannot convert to octal unambiguously - not multiple of 3 bits.
InterpretError: Cannot convert to hex unambiguously - not multiple of 4 bits.
InterpretError: floats can only be 32 or 64 bits long, not 7 bits

みたいな、ね。

Chapter 6 Slicing, Dicing and Splicing

slice もできます:

>>> a[10:20]
ConstBitArray('0b1000100110')

んー、でも bitstring っつーくらいだから普通に slice とると bit 列としての slice になっちゃうんです。が、8bit 単位 = byte 単位でと思うならば

>>> a[2:4:8]
ConstBitArray('0x6364')

と、slice の step のところに 8 と入れてあげれば OK、このとき slice の index と 1 つめと 2 つめは単位が bit でなくて byte になります。

str っぽく扱えるので例えば結合できます、かけ算もできます。:

>>> bitstring.ConstBitArray('bytes=abc') + bitstring.BitStream('bytes=def')
ConstBitArray('0x616263646566')
>>> bitstring.ConstBitArray('bytes=abc') + 'bytes=def'
ConstBitArray('0x616263646566')
>>> 2*_
ConstBitArray('0x616263646566616263646566'

join と split もできます。

Bit* であれば

  • append
  • prepend
  • slice で指定した範囲の del
  • insert
  • overwrite
  • replace (Chapter 7 で出てくる)

もできます。これらに対しても引数に auto initialiser が使えます。

Chapter 7 Reading, Parsing and Unpacking

こっちこそ真骨頂?

read/peek

クラスが *Stream であれば、Stream と扱えるということは、読んでる位置である pos という変数を持っていて、read で好きな長さ読みだすことができます。read は読むと pos が進むけど、peek は進みません。

>>> f = bitstring.ConstBitStream(filename='/usr/share/dict/words')
>>> f.pos
0
>>> f.read(64)
ConstBitStream('0x0a410a4127730a41')
>>> f.pos
64
>>> f.pos = 0
>>> f.read('int:64')
738883088816474689L
>>> f.pos = 0
>>> f.read('float:64')
2.7706698372861208e-259
>>> f.pos = 0
>>> f.read('bytes:8')
"\nA\nA's\nA"
>>> f.pos = 0
>>> f.readlist(['bytes:1', 'int:8', 'int:16', 'float:32'])
['\n', 65, 2625, 3.3728583026705486e-15]
>>> f.pos
64
>>> f.peek(64)
ConstBitStream('0x4f4c0a414f4c2773')
>>> f.pos
64
>>> f.peeklist(['int:32', 'folat:32'])
[1330383425L, 3425137408.0]
>>> f.pos
64
pos/bytepos

pos はもぉ見ましたが、8-bit を 1 step とする bytepos もあります。

>>> a = bitstring.ConstBitStream(bytes='abcdefghijklmn')
>>> a.pos
0
>>> a.pos += 10
>>> a.pos
10
>>> a.bytepos
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
  File "/usr/local/lib/python2.6/site-packages/bitstring/constbitstream.py", line 86, in _getbytepos
    raise ByteAlignError("Not byte aligned in _getbytepos().")
ByteAlignError: Not byte aligned in _getbytepos().

>>> a.pos += 6
>>> a.bytepos
2
>>> a.pos
16

pos が 8 の倍数でないとそりゃ bytepos で怒られます。

unpack

一方で unpack という method があってこれはみんなが使えます。pos が無くてもよくて、pos があってもいじりません。常に最初から読む感じです。

>>> f = bitstring.ConstBitArray(filename='/usr/share/dict/words')
>>> f.unpack('8*bytes:10, int:32, 2*bool, 4*float:32')
["\nA\nA's\nAOL", "\nAOL's\nAac", 'hen\nAachen', "'s\nAaliyah", "\nAaliyah's", '\nAaron\nAar', "on's\nAbbas", '\nAbbasid\nA', 1650614643L, False, True, -2.5086945375745037e-16, 2.9651225187188671e-14, -0.10244781523942947, 1.2934015138606442e-35]
find/rfind/findall

bitstring の検索もできます。

  • find は頭から探して最初の場所を
  • rfind は後から探して最初の場所を
  • findall は match する全ての場所を頭から答える generator を

返します。

>>> a = bitstring.ConstBitArray(bytes='abcdefghijklmn')
>>> a.find('int:7=45')
(70,)
>>> a.rfind('int:7=45')
(102,)
>>> tuple(a.findall('int:7=45'))
(70, 83, 102)

Chapter 8 Miscellany

細々した method の説明です。

bytealign
pos が 8 の倍数になるように、pos に range(7) から数字を足します。
reverse
bit 単位で順番をひっくり返します。破壊的。ひっくり返す範囲も指定できます。
tobytes
.bytes と同様ですが、length が 8 の倍数でない場合適当な数の '0b0' を append したものを返します。
tofile
mode が 'wb' で開かれてる file を引数にとって、保存します。tobytes 同様 padding します。
startswith
bool を返します。引数の auto initialiser で始まってるかどうかを返します。
endswith
startswith の終わってる番です。
ror
bit 単位で引数個だけ右に回します。破壊的。
rol
bit 単位で引数個だけ左に回します。破壊的。

あと、__ で始まる method の説明もあります。

感想

全然まとまってねーし、長ぇーし。