(私の)背景
ふだん、趣味のコードはSchemeで書いている。学生時代は、CとC++で書いていた。
そんな育ち方をすると、Pythonの文法は驚きに満ちている。そんな私の驚きを共有したい。系統的な説明にはなっていないので、あくまで読み物として楽しんでほしい。
Pythonのデータ型
先週の記事も、ぜひ読んでいただきたい。
strの要素はstr
以下のテストコードを見てほしい。
#!/usr/bin/python3
str_cat="CAT"
print(f"name:str_cat, type:{type(str_cat)}, value:{str_cat}")
# name:str_cat, type:<class 'str'>, value:CAT
char_A=str_cat[1]
print(f"name:char_A, type:{type(char_A)}, value:{char_A}")
# name:char_A, type:<class 'str'>, value:A
Cの感覚だと、文字列(string)str_catの要素を、indexで指定してとってきたら当然文字(char)'A'が手に入ると思いきや、文字列(string)"A"が手に入ってしまう。
なぜなら、Pythonにはchar型はないから。indexで指定すると「1文字の文字列」が手に入る。
付随して以下が起きる。
- シングルクォートでくくった'a'とダブルクォートでくくった"a"はまったく同じ。どちらも長さ1の文字列。
- ord()みたいな、文字1文字をとる関数に2文字以上の文字列を突っ込むと、実行時にTypeErrorが起こる(実行前の型チェックは通る)。
bytes型
bytes型はtuple型(1,2,3)でも、list型[1,2,3]でもない。
バイナリのデータがぎっしり詰まったもの。
list型ではないけど、同じ書式のindex指定で要素を取り出すことができる。取り出した要素は0~255の範囲の整数(int)。
bytesは、要素を列挙する形では作れない。listやb'文字列'を、bytes()に食わせると、bytes型の変数になる。つまり、bytes()はコンストラクタ。
b_l=bytes([1, 2, 3]) # listから作る
b_t=bytes((1, 2, 3)) # tupleから作る
b_s=b'abc' # 直接作る
bytesは、(Schemeの)mapができる
リスト内包表記 (List Comprehension)と呼ばれる文法がある。
bytes([b ^ 0xff for b in data])
は、
(map (lambda (b) (logxor b #xff)) data)
に相当する。loopで回りながら、bytes型のbから1byteずつ取り出して処理して、リストを作り、最後にbytes()でくるんでbytes型に戻している。
上の例では、引数が1つだけだけど、複数の引数をとりたいなら、次のように書く。zipでくるんで引数たちを渡してやればよい。
flag = bytes([x ^ y ^ z for x, y, z in zip(b_favorite, b_enc_favorite, b_enc_flag)])
bytes型のNOT(ビット反転)の取り方
bytesは直接ビット反転できない。~演算子は整数に対してのみ定義されているため。そのため、bytesでビット反転したいときは、0xFFとXORする。発想の転換。
data = b'\x01\x02\xff'
not_data = bytes([b ^ 0xff for b in data])
メソッドと関数
Pythonには、メソッドも関数もある。型に紐づく操作はメソッドになりがち。型によらない操作は関数の形になりがち。
bytes.fromhex()
strb_fox="464f58"
b2_fox=bytes.fromhex(strb_fox)
bytes.fromhex()は、文字列を2文字ずつ取って16進数と解釈してbytesを作る関数。
普通、メソッドは変数xにたいしてx.encode()の形で呼ばれるが、これは、bytesが頭についている。「代替コンストラクタ (Alternative Constructors)」と呼ばれるもので、bytesのコンストラクタの仲間扱い(アウトプットがbytes)なので、bytesが苗字としてついている。
結びに
世界にはたくさんの言語がある。だけど、Pythonにも、Schemeのmapに相当する概念があったり、言語は親戚でもある。比較文化論的に楽しみながら、Pythonとも仲良くなれたらいいと思う。