Python 2系で UTF-8 を取り扱う際の心得
Python 2系での日本語の扱い方について、忘れた頃に忘れてしまうのでメモ。
ソースで日本語(UTF-8)を使う
まずは基本から。
ソースの1行目または2行目にコメントを入れ、正規表現 coding[:=]\s*([-\w.]+) にマッチする文字列を書けばよい。
普通は以下のように書く。
shebang 付き (ここで Python のバージョンを指定したほうがよい場合もある)
3つの文字列クラス
Python 2系の場合、今扱っているオブジェクトが何者かを意識することが大切。
- str
ASCII文字列向けに設計された文字列クラスだが、その中にはテキスト以外も格納できるので
実態はバイト配列のようなものとなっている。
>>> 'abc'
'abc'
>>> '\x03'
'\x03'
>>> 'あいう'
'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
>>> type ( 'abc' )
< type 'str' >
|
- unicode
あらゆる文字コードのUnicode文字列を抽象化したクラス。
>>> u 'abc'
u 'abc'
>>> u '\x03'
u '\x03'
>>> u 'あいう'
u '\u3042\u3044\u3046'
>>> print u '\u3042\u3044\u3046'
あいう
>>> type (u 'abc' )
< type 'unicode' >
|
- basestring
基底の抽象クラス。
文字列クラスかどうかを判定する場合に使用。
>>> isinstance ( 'abc' , str )
True
>>> isinstance (u 'abc' , str )
False
>>> isinstance ( 'abc' , basestring )
True
>>> isinstance (u 'abc' , basestring )
True
|
2つのメソッド
str 型から unicode 型への変換をデコード、その逆をエンコードと呼ぶ。
その変換処理は str クラスのメソッドとして定義されている。
これらのメソッドは、encoding と errors という 2つの引数を取る。
- encoding: 変換に使用する codec 名。
デフォルトは defaultencoding の値。
defaultencoding は sys.getdefaultencoding() で取得できるもので、そのデフォルトは 'ascii'。
- erros: エラーハンドラ名。デフォルトは 'strict' (変換に失敗したら例外を発生)
明示的にエンコードする
厄介なエンコードエラーが発生する主な要因は、暗黙的なエンコード・デコード処理と
エンコーディングのミスマッチにある。
エンコード失敗の例>>> str (u 'あいう' )
Traceback (most recent call last):
File "<stdin>" , line 1 , in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0 - 2 : ordinal not in range ( 128 )
|
これは前述の str.encode() メソッドにおける encoding が省略された状態で実行されたのと同じ現象が起きている。
この場合は utf-8 でデコードされた unicode オブジェクト(u'あいう')が、
defaultencoding である ascii でエンコードされたためにエラーとなってしまったのだ。
とはいえ実行時に defaultencoding を変更するのは、不可能ではないが特別な理由が無い限りすべきではない。
他のモジュールへもその影響が波及してしまうため、思わぬトラブルを引き起こすリスクがある。
解決策の一つは、以下のように明示的にエンコード・デコードを行うこと。そうすればエラーは起こらない。
>>> str (u 'あいう' .encode( 'utf-8' ))
'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
|
"Zen of Python" の『曖昧さにあたっては推測をしたがるな』に従おう。
>>> import this
(略)
In the face of ambiguity, refuse the temptation to guess.
(略)
|
IO境界でラップする
Unicode の取り扱いに関するたった一つのベストプラクティスは、IO境界 --
つまりファイルの読み書きや、ネットワーク通信、画面出力などのタイミングで変換することだ。
- 入力のタイミングで、str から unicode にデコード
- 出力のタイミングで、unicode から str にエンコード
- 内部処理は全て unicode 文字列として取り扱う
Python 2.6 から利用可能になった io モジュールを使えばラッピングを簡便にしてくれる。
- ファイル入出力
>>> import io
>>> with io. open (FILEPATH, mode = 'w' , encoding = 'utf-8' ) as file :
... file .write(u 'あいう\n' )
...
4L
|
- 標準入出力
標準出力を io.open() で開き直せば、適切なエンコーディングが選択される。
>>> import io, sys
>>> stdout = io. open (sys.stdout.fileno(), 'w' )
>>> stdout.write(u 'あいう\n' )
あいう
4L
|
io.open() の場合、encoding パラメータを指定しなくても、環境に応じてよしなに
エンコーディングを判断してくれるのが素晴らしい。
実際には後述の locale.getpreferredencoding() の値が参照されているようだ。
Python クックブック(1.22章)には codecs.lookup を使った以下のような方法も掲載されていた。
>>> import codecs, sys
>>> old = sys.stdout
>>> sys.stdout = codecs.lookup( 'utf-8' )[ - 1 ](sys.stdout)
|
環境に応じたエンコーディングを取得する
Python が実行された環境の標準的なエンコーディングを動的に取得するにはどうすればよいか。
完全な方法ではないものの、以下の2種類のアプローチを順に試せば大半の環境で意図した動作となるようだ。
- ファイルの encoding 属性
属性が存在しない場合もあるので、getattr() を使ったほうが安全。
- 例) Mac (utf-8)
>>> import sys
>>> getattr (sys.stdout, 'encoding' , None )
'UTF-8'
|
- 例) Windows (SJIS)
>>> import sys
>>> getattr (sys.stdout, 'encoding' , None )
'cp932'
|
- 例) Solaris (SJIS)
>>> import sys
>>> getattr (sys.stdout, 'encoding' , None )
>>>
|
- locale.getpreferredencoding()
- 例) Mac (utf-8)
>>> import locale
>>> locale.getpreferredencoding()
'UTF-8'
|
- 例) Windows (SJIS)
>>> import locale
>>> locale.getpreferredencoding()
'cp932'
|
- 例) Solaris (SJIS)
>>> import locale
>>> locale.getpreferredencoding()
'PCK'
|
まとめ
- まず始めに、プログラムの設計段階で全てのIO境界を洗い出す
(図に書くのが一番よい)
- それぞれのIO境界において、どのエンコーディング(utf-8等)で
変換(decode, encode)を行うのか整理する
(同時に、環境に依存する部分や動的に取得する部分を明確にする)
- 明示的な変換(decode, encode)、明示的なエンコーディングの指定をする
(実行環境の defaultencoding (デフォルトはUS-ASCII) に依存しない実装をする)
(ライブラリ or 自作のラッパーを利用してもよい)
- それでも UnicodeDecodeError, UnicodeEncodeError が発生してしまったら、
変換前のデータ・データ型・変換しようとしたエンコーディングを再確認する
References