Pythonでprintの結果をリダイレクトする場合に出力のエンコーディングを指定する方法

Java-ja温泉でちょうどエンコーディングの話で盛り上がっていたので@whosaysniに聞いてみた。print文でunicodeオブジェクトを出力しようとすると、出力先のシェルのエンコーディングでバイト列に変換してから出力される。

tmp$ cat > tmp.py
print u'\u307b\u3052'
tmp$ python tmp.py 
ほげ

しかし、シェルの側でリダイレクトをしていると、シェルと違ってファイルのエンコーディングは簡単に取得できないので「エンコーディングわかんないから何で出したらいいかわかんないや。中身がasciiだったらそのまま出してもいいか」とasciiに変換しようとして失敗する。

tmp$ python tmp.py > tmp.txt
Traceback (most recent call last):
  File "tmp.py", line 1, in <module>
    print u'\u307b\u3052'
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

で、その解決方法だけど、そのスクリプトからリダイレクトして書き出すファイルのエンコーディングutf-8であると仮定できるなら:

import sys, codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

これでOK。試してみよう。

tmp$ cat > tmp.py
import sys, codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
print u'\u307b\u3052'
tmp$ python tmp.py > tmp.txt
tmp$ cat tmp.txt 
ほげ

めでたしめでたし。あと@tokibitoに chardet ってライブラリでエンコーディングの判定ができると教えてもらった。今回のケースには使わないけど必要になることもありそうだから覚えておこう。