Python2のstrは死んだ
Python3の概略をざっと聞くと、Python3ではPython2のユニコード型が文字列型になり、文字列型はバイト文字列型になった、というイメージを持ってしまう人も多いだろう。Python2の s="spamspamspam" は、Python3の s=b"spamspamspam" と同じだ、と。
しかし、Python3に"バイト文字列"なんて存在しない。あるのは "bytes"だ。複数形を示す "s"に注意しよう。bytesはバイトのコンテナであり、バイトとは整数値だ。バイトは文字ですらないのだ。
そう、だからもう認めよう。Python2のstrは死んだ。Python3にはもう存在しない。strは消え去った。お亡くなりになった。お隠れになった。成仏した。主の御許に召された。バージョン履歴に残るゴミクズとなった。その生涯は幕を閉じ、アンコールに応じることもないのである。
strとbytesの違い
とは言っても、Python2のstrとPython3のbytesで類似点は多い。特にbytes型のメソッドはstrのメソッドがほぼそのまま残されており、使えなくなっているのはencode()メソッドぐらいだ。
まずこの例を見てみよう。
s = b"abc" print(s[0]+s[1])
Python2.6/2.7でこのスクリプトを実行すると、結果は"ab"となる。しかし、Python3ではなんと195となるのだ。195は、ord('a')+ord('b')の値だ。つまりPython3のbytesオブジェクトでは、bytes_obj[idx]で"文字"ではなく、数値が返るのである。
>>> b'spam'[0] 115
イテレータでbytesオブジェクトの要素を取得しても同様だ。
>>> for c in b"spam": ... print(c) ... 115 112 97 109
str型やunicode型では、インデックスで要素を取得すると同じ型の値が返る。str_obj[0]はstr型の値となるし、unicode_obj[0]はunicode型のオブジェクトの値が返る。しかし、bytes型の場合には、byes型のオブジェクトではなく、int型のオブジェクトが返ってくるのだ。これは従来の文字列型とは全く異なる点で、よく理解しておかなければならない。
str型やunicode型は、文字列を表すプリミティブ型だが、bytes型はそうではない。int型オブジェクトを格納するコンテナオブジェクトにすぎないのだ。
このbytes型の動作は、文字列オブジェクトよりはarrayモジュールのarray.array('B')オブジェクトとほぼ同じだ。bytes型は、Python2の文字列型と考えるより、array.array('B')にstr型のメソッドを追加したもの、と考えた方が類似点は多いのである。
このため、通常、文字列で使うイディオムでbytes型では使えないものは多い。
b"spam"[0] == b"s"
は当然Falseだ。b"spam"[0]の値は数値の115であり、bytesオブジェクトのb"s"と同じ値であることはありえない。このような場合は
b"spam"[0] == ord("s")
と明示的に数値で比較するか、
b"spam"[0] == b"s"[0]
とどちらもインデックスで値を取得する、もしくは
b"spam"[0:1] == b"s"
のように、スライスで値を取得した場合はbytesオブジェクトが返るのを利用する。
また、
b = bstr[0:5]+bstr[7]+bstr[9:]
も駄目だ。
b = bstr[0:5]+bstr[7:8]+bstr[9:]
としなければならない。
他にも、
b"-".join(b"spam")
もb"s-p-a-m"にはならない。bytes.join()に渡せる引数は、bytesオブジェクトを返すイテレータだけだからだ。こう書きたければ、
b"-".join(bytes([c]) for c in b"spam")
とでもするしかないだろう。
ユニコードとの自動変換
Python3では、bytes型とstr型(Python2でのunicode型)の自動変換が廃止されている。
u"spam" + b"ham"
はPython2ではu"spamham"だが、Python3ではTypeErrorとなる。また、Python2で
>>> print(unicode(b"spam")) spam
であるが、Python3ではbytes型でも数値やリストと同様に
>>> print(str(b"spam")) "b'spam'"
となってしまう。これは、strに渡されたオブジェクトは全てrepr()で文字列に変換されてしまうからだ。byteオブジェクトをstrに変換する場合は、かならずエンコーディングを指定しなければならない。
>>> print(str(b"spam", "ascii")) spam
bytesオブジェクトとstrオブジェクトの比較では==による比較だけが許されており、
u"spam" == b"ham"
はPython2ではTrueだが、Python3ではFalseとなる。このため、Python2では{b'spam':1, u'spam':1}は
>> {b'spam':1, u'spam':1}
{'spam': 1}
と長さ1の辞書となっていたが、Python3では
>> {b'spam':1, 'spam':1}
{'spam': 1, b'spam': 1}
のように長さ2の辞書となる。