読者です 読者をやめる 読者になる 読者になる

Python2のstrは死んだ

python

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の辞書となる。