Help us understand the problem. What is going on with this article?

Python2で文字列を処理する際の心掛け

More than 5 years have passed since last update.

日本語を扱うPythonプログラマ(with Python2)にとっての最大の天敵(過言)、UnicodeEncodeError。
昨日横の人がその餌食となり、その解決の手助けをしているうちに自分の中でPython2での文字列処理の方向が少し整理できた。(近いうちにPython3バージョンもまとめたい)

個人的結論

  • バイト文字列/ユニコード文字列のどちらを扱っているかを常に意識しておく。
  • (基本的に)プログラム内ではユニコード文字列を扱い、標準入出力とやり取りする際(ex. print)にはバイト文字列に変換する。

バイト文字列とユニコード文字列

バイト文字列は特定のエンコード方式(ex. utf-8)でエンコードされており、リテラルでは'あいう'のように表現する。一方、ユニコード文字列はUnicodeのコードポイントを並べたものであり、リテラルではu'あいう'のようにuをつける。

(py2.7)~ » ipython
   (省略)
>>> 'あ' # バイト文字列
Out[1]: '\xe3\x81\x82'

>>> u'あ' # ユニコード文字列
Out[2]: u'\u3042'

>>> 'あ'.decode('utf-8') (or unicode('あ', 'utf-8')) # バイト文字列->ユニコード文字列(=デコード)
Out[3]: u'\u3042'

>>> u'あ'.encode('utf-8') # ユニコード文字列->バイト文字列(=エンコード)
Out[4]: '\xe3\x81\x82'

type関数で確認すると、バイト文字列はstr型/ユニコード文字列はunicode型であることが分かる。

>>> type('a')
Out[5]: str

>>> type(u'a')
Out[6]: unicode

さらにPython2では、バイト文字列とユニコード文字列はどちらも文字列であり連結可能である。

>>> u'a' + 'a'
Out[7]: u'aa'

なんだ。何の問題もないじゃない。

そう、日本語(正確には非ASCII文字全般)を扱わなければね!
前述の例の出力を見ると分かるが、ユニコード文字列とバイト文字列を結合するとユニコード文字列が生成される。その過程でバイト文字列をユニコード文字列にデコードしなければならないが、ここで問題となるのがPythonの文字列は自身のエンコードに関する情報を何も持っていないということだ。

「エンコード方式が分からないならASCIIでデコードしてしまえばいいじゃない」とPythonは言い、UnicodeEncodeErrorがこんにちわする。さすがにリテラル同士でこのようなミスをするのは稀だろうが、自身のプログラム外(標準入出力含む)から受けとる文字列に関しては気をつけないとやってしまいがち。

>>> u'a' + 'あ' # ユニコード文字列とバイト文字列(非ASCII)の結合
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-8-084e015bd795> in <module>()
----> 1 u'a' + 'あ'

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)

>>> u'a' + ''.decode('utf-8') # バイト文字列->ユニコード文字列
Out[9]: u'a\u3042'

>>> print(u'a' + ''.decode('utf-8'))
aあ

バイト文字列ではなくユニコード文字列に寄せるのは、文字列を操作する際にはバイトレベルよりもコードボイントレベルで処理する方が便利なことが多いためだ。例えば文字数をカウントしたい場合、ユニコード文字列ではlen関数で良い。一方バイト文字列ではバイト数が返ってくるため、その意図では使えない。

>>> len(u'あいう')
Out[11]: 3

>>> len('あいう')
Out[12]: 9

ユニコード文字列最高や!バイト文字列なんていらんかったんや!

とは、ならないのである。例として、以下のような簡単なプログラムを考えてみる。

test.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

print(u'あ' + u'いう')

ターミナルで実行してみる。おそらく大多数の人が問題なく実行できるだろう。

(py2.7)~ » python test.py
あいう

では、実行結果をファイルにリダイレクトするとどうか。以下のようにUnicodeEncodeErrorが発生する環境が多いのではないか。

(py2.7)~ » python test.py > test.txt
Traceback (most recent call last):
  File "test.py", line 4, in <module>
    print(u'あいう')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)

ロケールとエンコード方式

例としたprint(u'あいう')では標準出力にユニコード文字列を渡すが、この際、ユニコード文字列->バイト文字列の変換(エンコード)が行われる。
標準入出力がターミナルに接続してる場合はlocaleの値(ex. 環境変数LANG)から適切なエンコード方式をPythonが自動的に選択してくれる。一方、リダイレクトなどで標準入出力をターミナル以外に接続した場合、適切なエンコード方式を選択するための情報が得られず、ASCIIでエンコードを試み、大抵の場合(=非ASCII文字を含む場合)は失敗する。

(ref.) http://blog.livedoor.jp/dankogai/archives/51816624.html

標準出力に渡す前にユニコード文字列をエンコードすれば、この問題は解決できる。

test.py(ユニコード文字列->バイト文字列)
#!/usr/bin/env python
# -*- coding: utf-8 -*-

print((u'あ' + u'いう').encode('utf-8'))

と今まで思っていたが

PYTHONIOENCODINGという環境変数を指定することで、ロケールによらずに使用するエンコード方式を固定できるとのこと。これを指定すれば、いちいちエンコードしなくて良さそう。

(py2.7)~ » cat test.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

print(u'あ' + u'いう')
(py2.7)~ » PYTHONIOENCODING=utf-8 python test.py > test.txt
(py2.7)~ » cat test.txt
あいう

(ref.) http://methane.hatenablog.jp/entry/20120806/1344269400

FGtatsuro
I'm mainly interested in Python, Objective-C, Java, Android.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした