2011-09-21
■[Python] file.write()
時の UnicodeError
対策
sys.stdout.write(u'ほげ')
などとした際に UnicodeError
を食らわないための対策。
Cygwin 1.7.9-1, Python 2.6.5-2 で調査したものです。Windows環境、およびPython3については触れません。また、ソースコードのエンコーディング*1は適切に設定されているものとします。
最も簡単なのはUnicode文字列を一切使わず、全てByte文字列で処理することです。ただしこれだと文字列処理が必要な場合面倒なことになるので、やはりUnicode文字列のみを使いたいというケースが多いと思います。
結論から言うと、スクリプトの冒頭で以下のようにするのがよさそうです:*2 *3
ENC_LOCALE = locale.getpreferredencoding() sys.stdout = codecs.getwriter(ENC_LOCALE)(sys.stdout, errors='replace') sys.stderr = codecs.getwriter(ENC_LOCALE)(sys.stderr, errors='replace')
sitecustomize.py 内で sys.setdefaultencoding()
するのは避けるべきです。移植性を損なう上に、デフォルトエンコーディングに依存するライブラリが存在する可能性もあるからです。これは最後の手段とみなすべきでしょう。
以下はPythonのエンコーディング決定の仕組みと、Byte文字列/Unicode文字列の自動変換についてのメモ。
エンコーディング決定の仕組み
sys.getdefaultencoding()
デフォルトで 'ascii'
。ただし sitecustomize.py 内で sys.setdefaultencoding()
による設定が可能(前述の通り、これは最後の手段)。
locale.getpreferredencoding()
, sys.getfilesystemencoding()
LC_CTYPE
環境変数に従う。LC_CTYPE=C
の場合、'ANSI_X3.4-1968'
。
標準ストリーム(sys.stdin
, sys.stdout
, sys.stderr
)の encoding
属性
PYTHONIOENCODING
環境変数が存在すれば常にそれに従う。さもなくば
- 接続先が端末ならば
locale.getpreferredencoding()
に従う - 接続先が非端末(リダイレクトなど)ならば
None
なお、標準ストリームのUnicodeエラーハンドラ(errors
属性)は PYTHONIOENCODING
環境変数が存在すればそれに従う。さもなくば None
。
標準ストリーム以外のファイルオブジェクト
encoding
, errors
ともに None
。
エンコーディングを指定してファイルを開きたい場合は io.open()
を使う(この関数に encoding
引数を渡さなかった場合のエンコーディングは locale.getpreferredencoding()
に従う)。また、既存のファイルオブジェクトに対してエンコーディングを指定したい場合は out = codecs.getwriter('utf-8')(out)
のようにラップする。
Byte文字列とUnicode文字列の自動変換
print
文
対象ストリームの encoding
属性に従ってUnicode文字列を自動エンコードする。
file.write()
sys.getdefaultencoding()
に従ってUnicode文字列を自動エンコードする。対象ストリームの encoding
属性は無関係。
ただし、Python2.7以降では対象ストリームの encoding
属性に従ってUnicode文字列を自動エンコードする(http://bugs.python.org/issue4947)。
codecs.StreamWriter.write()
, io.TextIOBase.write()
対象ストリームの encoding
属性に従ってUnicode文字列を自動エンコードする。
その他
文字列結合/比較、%
演算子による文字列展開などでも自動変換が行われるが、これらは基本的にByte文字列とUnicode文字列を混在させるべきでない。
参考資料
*1:# coding: utf-8
のようなコメントのこと。なお、-c
オプションによるワンライナのエンコーディングは常に latin-1 と仮定される模様。
*2:errors='replace'
を指定するのは、出力エンコーディングで表現できない文字が含まれていた場合に UnicodeEncodeError
となるのを避けるため。
*3:ただし、このように codecs.StreamWriter
でラップした場合、write()
にByte文字列をそのまま渡すことはできなくなるので注意(常にUnicode文字列に変換してから処理され、このとき変換に失敗すると UnicodeDecodeError
となる)。
- 31 http://www6.atpages.jp/appsouko/
- 18 https://www.google.co.jp/
- 17 http://www.google.co.jp/url?sa=t&rct=j&q=file.Write&source=web&cd=2&ved=0CCMQFjAB&url=http://d.hatena.ne.jp/taotao1942/20110921/1316584741&ei=uwe6TobcIab-mAXCkv2JCA&usg=AFQjCNG1ThVEbii6W7BpWWnuC3eiDNyw8g
- 13 http://d.hatena.ne.jp/
- 13 http://d.hatena.ne.jp/atsuoishimoto/20110311/1299805971
- 11 http://search.yahoo.co.jp/search?p=ダブルムーン伝説&tid=top_ga1_sa&ei=UTF-8&aq=-1&pstart=1&fr=top_ga1_sa&b=11
- 10 http://www6.atpages.jp/~appsouko/
- 9 http://www.google.co.jp/url?sa=t&rct=j&q=ダブルムーン&source=web&cd=18&ved=0CFIQFjAHOAo&url=http://d.hatena.ne.jp/taotao1942/20110909/1315530639&ei=tIDHTpS4EJHJmAXXvLUv&usg=AFQjCNF0_GMu1KM2bbeNt6k5pFas
- 8 http://k.hatena.ne.jp/keywordblog/StreamWriter
- 8 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=12&ved=0CF4QFjABOAo&url=http://d.hatena.ne.jp/taotao1942/20110921/1316584741&ei=aey8T9zjCpCXiQeem-jVDw&usg=AFQjCNG1ThVEbii6W7BpWWnuC3eiDNyw8g&sig2=oA5jay14vR9DZtfV1lxROg