2013年12月12日木曜日

標準出力先の文字エンコードが異なってもPythonのコードを変更せずに済む方法


問題:

Python 2.7を使用してSublime Text 2でBuildした際に,
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-7: ordinal not in range(128)
となる.

条件:

マルチバイト文字を使用した,標準出力を行っている.

原因:

unicode型をstr型のエンコードが正常にいっていない.

解決策:

以下を,標準出力が行われる前に貼付.
import locale,codecs
enc = locale.getpreferredencoding();
sys.stdout = codecs.lookup(enc)[-1](sys.stdout)


解説:


Python 2.7 で日本語などのマルチバイト文字を扱う場合,次のように'の前にuを付けて使用する.
uni = u'日本語の文字列'

この変数uniを標準出力に表示させる場合,単に
print uni
とすることで大抵は正常に実行される.

ここで,実際に行われていることは,例えばコマンドプロンプトであれば
print uni.encode('cp932')
というように,cp932の文字コードでuniをエンコードした結果をprintしている.

このときに,エンコーディングに使用される文字コードは
sys.stdout.encoding
から参照することができる.


では,なぜSublime Text 2上でBuildするとエラーが発生したのか.
私の環境で,sys.stdout.encodingを参照してみたところ,帰ってきた値は
None
だった.

この場合,Python 2.7 ではunicode型からstr型からのエンコーディング時に
デフォルトでasciiを使用する.

当然ながら,日本語はasciiに存在しない文字であるため,エラーが発生ししてしまう.

私の環境ではSublime Text 2の標準出力はutf-8を利用しているので,
print uni.encode('utf-8')
と明示的に文字コードを指定してあげれば,正常に表示される.

しかしながら,このコードをそのままコマンドプロンプトなどの
標準出力文字コードが異なるターミナル上で実行すると
文字化けが起きてしまう(当たり前だが).


とりあえず,ネットの情報から以下のコードを追加すると,
他のターミナルでもある程度はそのまま表示されると思う.

enc = locale.getpreferredencoding();
sys.stdout = codecs.lookup(enc)[-1](sys.stdout)
ここで
enc = locale.getpreferredencoding()
そのターミナルで使用している文字コードを取得(?).

sys.stdout = codecs.lookup(enc)[-1](sys.stdout)
で,標準出力で使用する文字コードを指定する.

参考