このBlogは移転しました。今後は aish.dev を御覧ください。

備忘録 - #python3 で sys.std(in|out|err) の encoding を強制する について

備忘録 - #python3 で sys.std(in|out|err) の encoding を強制するについて

C言語の教科書にでも出てきそうなこんな基礎的なスクリプト

import sys
for l in sys.stdin:
    print l

が動かない!って文句を言いたくなる気持ちは、非常によく理解できる。Unix的なというかC言語的には伝統的かつ完璧なイディオムで、これが正常に動作しないなんてどうかしてる。どんなプログラミング言語でも、Hello Worldの次ぐらいにはこんなプログラムを書いて、IOの使い方を試してみるものだ。

しかしながら、現代はもうUnicode時代なわけで、いや、少なくともPython3はUnicode時代であると定めてしまったわけで、K&R時代ならなんの問題もなかったこんな処理でもエラーになってしまう、そんな時代に我々は生きているのである。

何が問題かと言えば、もちろん、環境変数LANGCに設定してから実行している点だ。Pythonさんにしてみれば、「LANGはCですよ、これからあなたにお渡しするテキストはASCII文字列ですよ」とOSさんに言われて、はい判りましたとsys.stdinを読んでみれば、「小飼弾」という毛深い文字が流れ込んできた。これにはさすがのPythonさんもエラーのひとつもはいて抗議しようと言うことになってしまうだろう。

昔風の「日本語アプリケーション」なら、LANGがなんだろうとConfigureのオプションで指定したエンコーディングを優先して無理矢理使ったりしただろうが、Python環境変数の方を優先する。環境変数でASCIIだっって言ってるくせに、読めないテキスト渡したって知らないよっということだ。

で、対処方法ですが、ご本人も

バイト列として読もうにもMSBが立っているだけで殺されてしまうのです。

http://blog.livedoor.jp/dankogai/archives/51816624.html

書かれているとおり、バイト列として読み込めば良い。だけど、sys.stdinというのはテキストIOのためのAPIで、必ずバイト列->Unicodeの変換処理が動いてしまう。

したがって、ロケールに関わりなく、バイト列が欲しいのであれば、バイトIOのためのAPIを使って

import sys

for l in sys.stdin.buffer:
    sys.stdout.buffer.write(l)

とすれば良いだけの話だ。テキストIOを使ってバイト列を取得しようとするのは無茶だ。くれぐれも言っておくが、Python3では文字列とバイト列は完全に別物である。C言語などでの、文字ってのは8ビットの数値だ、みたいな感覚は完全に忘れて欲しい。

ちなみに、sys.stdoutなどの、テキストファイルのエンコーディングをオープンしたあとに変更したいという話はもちろんあって、実はioモジュールのPEP でも元々変更可能とされていたのだけど、実装の都合で後回しにされてしまったらしい。最近、またその話が出てきている ので、近いうちにエンコーディングが変更可能となるんじゃないかと予想している。