如何在Python 3中设置sys.stdout编码?

在Python 2中设置默认的输出编码是一个众所周知的习惯用法:

sys.stdout = codecs.getwriter("utf-8")(sys.stdout) 

这将sys.stdout对象包装在编码输出为UTF-8的编解码器sys.stdout器中。

但是,这种技术在Python 3中不起作用,因为sys.stdout.write()需要一个str ,但是编码的结果是bytes ,而当codecs试图将编码的字节写入原始的sys.stdout时会发生错误。

什么是在Python 3中做到这一点的正确方法?

Python 3.1添加了io.TextIOBase.detach() ,在sys.stdout的文档中有一个注释:

标准stream默认处于文本模式。 要将二进制数据写入或读取到这些数据,请使用基础二进制缓冲区。 例如,要将字节写入stdout ,请使用sys.stdout.buffer.write(b'abc') 。 使用io.TextIOBase.detach()stream可以默认为二进制。 这个函数设置stdinstdout为二进制:

 def make_streams_binary(): sys.stdin = sys.stdin.detach() sys.stdout = sys.stdout.detach() 

因此,Python 3.1及更高版本的相应习语是:

 sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach()) 

在Python 2中设置默认的输出编码是一个众所周知的习惯用法

伊克! 这是Python 2中一个众所周知的习惯用法吗? 这对我来说是一个危险的错误。

它肯定会搞乱任何试图编写二进制文件到stdout的脚本(例如,如果你是一个CGI脚本返回一个图像,你将需要)。 字节和字符是完全不同的动物; 将指定接受字节的接口与仅接受字符的接口进行猴式修补并不是一个好主意。

CGI和HTTP通常明确地使用字节。 你只应该发送字节到sys.stdout。 在Python 3中,这意味着使用sys.stdout.buffer.write直接发送字节。 对页面内容进行编码以匹配其charset参数应该在应用程序中的更高级别处理(在您要返回文本内容而不是二进制文件的情况下)。 这也意味着print对于CGI来说已经不是什么好事了。

(为了增加混淆,wsgiref的CGIHandler在py3k中已经被打破,直到最近,这使得无法以这种方式将WSGI部署到CGI上,使用PEP 3333和Python 3.2,这是最终可行的。)

我发现这个线程,同时寻找相同的错误的解决scheme,

已经提出的另一种解决scheme是 Python启动之前设置PYTHONIOENCODING环境variables,供我使用 – 这样在Python初始化之后交换sys.stdout就不那么麻烦了:

 PYTHONIOENCODING=utf-8:surrogateescape python3 somescript.py 

有了不必去编辑Python代码的好处。

其他答案似乎build议使用codecs ,但open我的作品:

 import sys sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8', buffering=1) print("日本語") # Also works with other methods of writing to stdout: sys.stdout.write("日本語\n") sys.stdout.buffer.write("日本語\n".encode()) 

即使我用PYTHONIOENCODING="ascii"运行它,这也可以工作。

sys.stdout在Python 3中处于文本模式。因此,您直接编写unicode,不再需要Python 2的成语。

在Python 2中这将失败:

 >>> import sys >>> sys.stdout.write(u"ûnicöde") Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode character u'\xfb' in position 0: ordinal not in range(128) 

但是,它在Python 3中的工作很简单:

 >>> import sys >>> sys.stdout.write("Ûnicöde") Ûnicöde7 

现在,如果你的Python不知道你的stdout编码究竟是什么,这是一个不同的问题,很可能在Python的构build。

使用detach()会导致解释器在退出之前尝试closuresstdout时显示警告:

 Exception ignored in: <_io.TextIOWrapper mode='w' encoding='UTF-8'> ValueError: underlying buffer has been detached 

相反,这对我来说很好:

 default_out = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') 

(当然,写入default_out而不是stdout。)