[+]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 = 'あ'
$ 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行目に書いて代替のエンコード形式を教えてあげることで、このエラーを回避できる。
# -*- 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ファイルを作成して実行してみる。
# -*- 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でエンコードできません」というエラーに遭遇することになる。
ここまで聞けば対処法はもうお分かりだろう。
# -*- 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系では文字列周りが凄くシンプルになった。