2009年2月3日火曜日

sys.stdoutをbinaryでかく。

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
cgiでsys.stdoutへの出力がcp932で出てしまって困った人への回答@mixi

それなりにがんばって書いたのと、前の発表の内容のリサイクルが含まれるのでおいらが書くのが相当かとおもって書いてみました。コード書いたほうがいいかもね。でもまったくAPIが無いのも変だな。もうちょっとちゃんと探すべきかも。

追記あり。

http://coreblog.org/ats/article-of-python3-at-atmark-it
http://www.atmarkit.co.jp/fcoding/articles/python3/01/python301b.html
の話かな?


> print(b'文字列')も一応は試したのですが、これもTypeErrorを出したように記憶しています。また後日、試してみます。

b'...'は文字列ではないです。byte列です。:)
encodingがないのです。逆にbを指定しないものは文字列なので、encodingを持っています。

i.e.
>>> x = 'ほげ'
>>> x
'ほげ'
>>> x.encode('utf8')
b'\xe3\x81\xbb\xe3\x81\x92'

で、
>>> sys.stdout
<io.TextIOWrapper object at 0x7f6a78342690>
といわれます。

textモードで開いたfile objectはエンコードを持っています。
http://git.tonic-water.com/works/PythonCodeReading/2.5to3ofStdLib/PCR07.swf
クラス図はこちら
http://git.tonic-water.com/works/PythonCodeReading/2.5to3ofStdLib/io-class.dia

私の環境では、
>>> sys.stdout.encoding
'UTF-8
>>> locale.getpreferredencoding()
'UTF-8''
>>> print(sys.stdout._encoder)
<encodings.utf_8.IncrementalEncoder object at 0x7f6a7824a9d0>

>>> print(sys.version)
3.0+ (unknown, Jan 9 2009, 23:24:15)
[GCC 4.1.2 20071124 (Red Hat 4.1.2-42)]

uname -a
Linux asama 2.6.28.2 #28 SMP Tue Jan 27 15:03:26 JST 2009 x86_64 x86_64 x86_64 GNU/Linux
env.
LANG=en_US.UTF-8

念のためwindows xpのコマンドプロンプトから3.0を実行してみたところ、
sys.stdout.encodingはcp932でした。
>>> print(sys.stdout._encoder)
<encodings.cp932.IncrementalEncoder...>
なのでまあ、当然cp932を吐くでしょう。

となると・・・行儀よく清く正しいsys.stdoutのencoderの差し替え方がなんであるかが、問いですね。もしくはsys.stdoutをbinaryで開いて、utf8で明示的にencodeしてbyte列を生成して書き込むかですね。

さすがに
>>> sys.stdout.buffer.raw
_fileio._FileIO(1, 'wb')
にたいしてwriteするのはどうかと・・・。

なので、encodingがTextIOWrapperで付与されていることに着目して、
>>> print(codecs.getwriter('utf8').__init__.__doc__)
Creates a StreamWriter instance.

stream must be a file-like object open for writing
(binary) data.

stdoutのTextWrapperを「剥いて」あげてからStreamWriterのコンストラクタに渡してみるのはどうでしょう。

ということは
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
では駄目でしょうね。encoderが二段重ねになるので。


追記


sys.stdout.bufferを使うのが正しい。気持ち悪いが。

http://docs.python.org/3.0/library/sys.html#sys.stdout


Note

The standard streams are in text mode by default. To write or read binary data to these, use the underlying binary buffer. For example, to write bytes to stdout, use sys.stdout.buffer.write(b'abc').

0 件のコメント: