Edited at

Python2とPython3のstr型/unicode型/bytes型/encode/decodeの違い解説

More than 1 year has passed since last update.


[+]Python2

まず、Python2での挙動を確かめていく。


str型

Python2のstr型はエンコード形式不明の文字列である。

>>> 'あ'

'\xe3\x81\x82'

ここではe38182になっているのでutf-8でエンコードされているとわかる。

しかし、その「utf-8でエンコードされている」という情報は持っていない。

なので、以下のようなエラーが発生する。

>>> 'あ'.decode()

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)

エンコード情報を持っていないが故に、「ASCIIでエンコードされてるっしょ」という決め打ちでデコードしようとした結果、失敗したのだ。

次のようにすることで、正しくデコードできる。

>>> 'あ'.decode('utf-8')

u'\u3042'

こちらが引数で指定してあげる必要がある。

また、同じく「エンコード情報を持っていない」というのが原因で、次のようなエラーも出る。

以下のpyファイルを作成して、実際に実行させてみよう。


hoge.py

hoge = 'あ'


$ python2 hoge.py

File "hoge.py", line 1
SyntaxError: Non-ASCII character '\xe3' in file hoge.py on line 1, but no encoding declared;
see http://python.org/dev/peps/pep-0263/ for details

このエラーは何なのだろうか。

実は、ASCIIで表すことができない文字をソースコード中で使用すると、その(ソースコード中の)文字をどのエンコード形式でエンコードすればいいのかわからず、このエラーが出る。

「ASCIIで表せないけど代替のエンコード形式教えてもらってないよ!」と怒られているのである。

# -*- coding: utf-8 -*-という1文を1行目もしくは2行目に書いて代替のエンコード形式を教えてあげることで、このエラーを回避できる。


hoge2.py

# -*- coding: utf-8 -*-

hoge2 = 'あ'

$ python2 hoge2.py

ちなみに、Python3ではデフォルトのcodingがutf-8になっているため、この対応は必要なくなる。

次に、str型にindexを指定した場合を見てみる。

>>> 'a'[0]

'a'

>>> 'あ'[0]
'\xe3'

>>> 'あ'[1]
'\x81'

>>> 'あ'[2]
'\x82'

str型はエンコード情報を持たないため、'あ'という文字が内部的に何バイトになっているかを知る術が無い。

なので、str型にindexを指定すると、1バイトごとに区切られてしまう

次のように、一度unicode型にデコードして処理したあとエンコードして元に戻してあげる必要がある。

実はASCII以外の場合は、unicode文字列のほうが扱いやすいのである。

(ASCIIは必ず1バイトなので、1バイトごとに切り分けられても問題が無い。)

>>> 'あいう'.decode('utf-8')[0].encode('utf-8')

'\xe3\x81\x82'

>>> '\xe3\x81\x82' == 'あ'
True


unicode型

unicode型はunicodeのコードポイントを羅列した形式の文字列である。

>>> u'あ'

u'\u3042'

>>> u'あいう'
u'\u3042\u3044\u3046'

encodeしてみる。

>>> u'あ'.encode()

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3042' in position 0: ordinal not in range(128)

>>> u''.encode('utf-8')
'\xe3\x81\x82'

これもまた、ASCII決め打ちしているので引数で指定してあげる必要があるようだ。

次に、以下のpyファイルを作成して実行してみる。


fuga.py

# -*- coding: utf-8 -*-

fuga = u'あ'
print(fuga)

$ python2 fuga.py

$ python2 fuga.py > fuga.txt
Traceback (most recent call last):
File "fuga.py", line 3, in <module>
print(fuga)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3042' in position 0: ordinal not in range(128)

リダイレクトするとエラーになる。

これは何が原因なのだろうか。

「unicode文字列をASCIIにエンコードできません」と怒られているが、ちゃんと# -*- coding: utf-8 -*-って書いてるじゃないか。何故utf-8ではなくASCIIでエンコードしようとしてるんだ。と思うかもしれない。

しかし、# -*- coding: utf-8 -*-の意味はあくまで「ソースコード中の文字列のエンコード形式」だけである。

リダイレクト無しのターミナル直接出力でエラーにならなかったのは、実は「ターミナルのエンコード形式」を自動で取得して自動でエンコードして出力してくれたからなのである。

ターミナルのエンコード形式は次のコマンドで取得できる。

$ echo $LANG

en_US.UTF-8

utf-8と出力された。

つまり、ソースコード中のu'あ'はprintの際にutf-8に自動でエンコードされていたのだ。

しかし、$ python2 fuga.py > fuga.txtとリダイレクトするとどうだろう。

この時に重要になるのはターミナルのエンコード形式ではなく、fuga.txtのエンコード形式なのである。

なので、「リダイレクト先のfuga.txtのエンコード形式を教えてもらってないってことはASCIIかな」ということで相変わらずの決め打ちASCIIエンコードをかました結果、「ASCIIでエンコードできません」というエラーに遭遇することになる。

ここまで聞けば対処法はもうお分かりだろう。


fuga2.py

# -*- coding: utf-8 -*-

fuga2 = u'あ'
print(fuga2.encode('utf-8'))

$ python2 fuga2.py > fuga2.txt

そう。ソースコード中で(エンコード可能な形式で)エンコードしてしまえば良いのだ。


豆知識

PYTHONIOENCODINGという環境変数にエンコード形式を格納することで、リダイレクト時のエンコード形式を指定することができる。

$ export PYTHONIOENCODING='utf-8'

$ python2 fuga.py > fuga.txt


[+]Python3

それでは、Python3ではどうなのだろうか。


str型

>>> 'あ'

'あ'

>>> 'あ' == u'\u3042'
True

Python2ではstr型は何らかの形式でエンコードされた文字列という定義だったため、

あくまでも「文字列」ではなく「データ」として認識されていた。

しかし、Python3ではunicode文字列と固定されているので、一文字を一文字として認識することができる

なので当然、次のようなこともstr型の状態ですんなりできる。

>>> 'あいう'[0]

'あ'

>>> 'あいう'[1]
'い'

>>> 'あいう'[2]
'う'

エンコードするには、Python2同様encode関数を使用する。

>>> 'あ'.encode()

b'\xe3\x81\x82'

>>> 'あ'.encode('utf-16')
b'\xff\xfeB0'

ここで出てくるのがPython3で新たに加わった「bytes型」である。

(ちなみに、b'\xff\xfeB0' == b'\xff\xfe\x42\x30'である。)


bytes型

bytes型とは、「1バイト区切りのデータの羅列を表す型」のことである。

Python3では、どんな形式でエンコードされていようとも、エンコードされていればそれは文字ではなく「データ」として認識される。

index指定してみる。

>>> 'あ'.encode('utf-8')[0]

227

>>> hex('あ'.encode('utf-8')[1])
'0x81'

>>> hex('あ'.encode('utf-8')[2])
'0x82'

>>> 'あ'.encode('utf-8')[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: index out of range

お分かりだろうか。

bytes型にindexを指定すると1バイトごとに区切られ、更にそれは「数値」になる。

足してみる。

>>> b'\x01' + b'\x02'

b'\x01\x02'

index指定する前はまだ「バイトデータ」のまま。

なので、文字列連結のような動作をする。

もちろん次のように、バイトデータをデコードすることでunicode文字列を生成することもできる。

>>> b'\xe3\x81\x82'.decode('utf-8')

'あ'


[+]まとめ

Python2でのencode/decodeは「str型(何らかの形式でエンコードされた文字列)」と「unicode型(unicode文字列)」を行き来するモノ。

Python3でのencode/decodeは「str型(unicode文字列)」と「bytes型(バイトデータ)」を行き来するモノ。

文字列かバイトデータかの区別だけで済むようになったため、3系では文字列周りが凄くシンプルになった。