Python
python3
Python2
unicode型
bytes型

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系では文字列周りが凄くシンプルになった。