file.write() 時の UnicodeError 対策

sys.stdout.write(u'ほげ') などとした際に UnicodeError を食らわないための対策。
Cygwin 1.7.9-1, Python 2.6.5-2 で調査したものです。Windows環境、およびPython3については触れません。また、ソースコードエンコーディング((# coding: utf-8 のようなコメントのこと。なお、-c オプションによるワンライナのエンコーディングは常に latin-1 と仮定される模様。))は適切に設定されているものとします。

最も簡単なのはUnicode文字列を一切使わず、全てByte文字列で処理することです。ただしこれだと文字列処理が必要な場合面倒なことになるので、やはりUnicode文字列のみを使いたいというケースが多いと思います。

結論から言うと、スクリプトの冒頭で以下のようにするのがよさそうです:((errors='replace' を指定するのは、出力エンコーディングで表現できない文字が含まれていた場合に UnicodeEncodeError となるのを避けるため。)) ((ただし、このように codecs.StreamWriter でラップした場合、write() にByte文字列をそのまま渡すことはできなくなるので注意(常にUnicode文字列に変換してから処理され、このとき変換に失敗すると UnicodeDecodeError となる)。))

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 属性

[http://docs.python.org/using/cmdline.html#envvar-PYTHONIOENCODING:title=PYTHONIOENCODING] 環境変数が存在すれば常にそれに従う。さもなくば

  • 接続先が端末ならば locale.getpreferredencoding() に従う
  • 接続先が非端末(リダイレクトなど)ならば None

なお、標準ストリームのUnicodeエラーハンドラ(errors 属性)は [http://docs.python.org/using/cmdline.html#envvar-PYTHONIOENCODING:title=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文字列を混在させるべきでない。