methaneのブログ

このブログに乗せているサンプルコードはすべてNYSLです。

Python 3 の標準入出力のエンコーディング

404 Blog Not Found:備忘録 - #python3 で sys.std(in|out|err) の encoding を強制する

というわけでこうなりました。

import sys
sys.stdin =  open('/dev/stdin',  'r', encoding='UTF-8')
...

無理矢理開き直すなんてずいぶんと乱暴だと私も思うのですが、

こういう強引な方法がちゃんと動くのがLLのいいところですね。 /dev/stdin だとWindowsでは動かないので、 open(sys.stdin.fileno(), ...) がいいと思います。

ぐぐれど探せどこれしか解答が思いつかず。

PYTHONIOENCODING という環境変数を使えば locale を無視して標準入出力のエンコーディングを指定することができます。
Pythonを使う時の基礎になるので、 Python Setup and Usage — Python v3.3.0rc2 documentation は一度目を通しておいて損はありません。

Python 2では以下でうまく行ってたのですが…

import sys

for line in sys.stdin:
    chars = list(line.rstrip().decode('utf-8'))
    print(u''.join(chars).encode('utf-8'))

Python 3 では Unicode と バイト列を混ぜられなくなったので、少し面倒ですが、バイト列として入出力する場合は .buffer プロパティでバイトストリームにアクセスする必要があります。

#encoding:utf-8
import sys

for line in sys.stdin.buffer:
    line = line.rstrip().decode('utf-8')
    sys.stdout.buffer.write((''.join(line) + '\n').encode('utf-8'))

こういうlocaleに頼れない環境は、レンタルサーバーなどではむしろ当然とも言えます。にもかかわらずぐぐってもこれといった解答に出会えなかったのは、まだPython 3がさほど普及していないってことでしょうか。

locale の使えないレンタルサーバーを使う機会ってここ数年めっきり減ってしまったので、それで検索しても回答に辿りつけないかもしれないです。
この問題はリダイレクトの場合によくぶち当たります。localeを見てエンコーディングを判別するのは標準入出力の接続先がターミナルだった場合だけで、ファイルやパイプに流しこむときに正しいエンコーディングPythonは知らないからです。(試しに "python3 redirect encoding error" で検索するとすぐに API Only - Stack Exchange が見つかり、 PYTHONIOENCODING も buffer も回答に挙がっていました。)
なので、常に utf-8 を使って欲しい環境では、 PYTHONIOENCODING=utf-8 を指定しておくことをおすすめします。

追記
Python 3.2 からは出力をリダイレクトしても locale を見てくれるようになっていました。3.1以前は確認していませんが、基本的に3.1以前の Python 3 はなかったことにしていい(Python 3 対応するときの 3系のベースラインは3.2が主流)ので、リダイレクトしたらASCIIになるのは Python 2 特有の罠です。