Python3开始区分string和bytes之后,写代码时需要考虑的问题便多了…… 这回在Windows下碰到了一个stdout输出时编码错误的问题。
输出你好,世界!
遇到的问题
有下面一个简单的Python3程序msg.py
,输出你好,世界!
这几个字:
# vim:fileencoding=utf-8
msg = "你好,世界!"
print(msg)
在Python脚本中可以在第一行使用
# -*- coding: <encoding-name> -*-
或者# vim:fileencoding=<encoding-name>
来指定文件编码。前者是emacs风格,后者是vim风格。
在Windows的Cmd.exe运行的时候,是正常。但是一旦重定向到一个文件,比如C:\> python msg.py > out1
,就会出现一个错误
> python msg.py > out1
Traceback (most recent call last):
File "msg.py", line 9, in <module>
print(msg)
File "C:\Python36\lib\encodings\cp1252.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-5: cha
racter maps to <undefined>
把stdout重定向到一个文件的时候竟然编码错误,依据错误显示,应该是使用了cp1252
这种编码格式来编码了utf-8
的内容呢。
不知道这跟我使用的操作系统有没有关系,我使用的是英文的Windows,用chcp
命令显示的code page是437。可是就算我用chcp 65001
把代码页切换成代表utf-8的65001,结果也不行。
于是Google了一下,发现了这个帖子:[How to set sys.stdout encoding in Python 3?]。 帖子的内容说到,如果是Python2的话,用下面的方式重新定义一下stdout就可以了:
sys.stdout = codecs.getwriter("utf-8")(sys.stdout)
当时在Python3中有一些小复杂,有几种方式可以修正这个问题。
设置PYTHONIOENCODING环境变量
依据下面的方法,设置PYTHONIOENCODING环境变量,结果就OK了:
set PYTHONIOENCODING=utf-8:surrogateescape
python msg.py > out1
# 没有出现错误
stdout.buffer.write
第二个步骤是越过stdout的编码转换,直接往其buffer里面写入内容:
# vim:fileencoding=utf-8
import sys
msg = "你好,世界!"
print(sys.stdout.encoding)
sys.stdout.buffer.write(msg.encode('utf-8'))
sys.stdout.buffer.write
直接把内容写到buffer中,错误没有出现,但是输出的内容却是如下:
你好,世界!cp1252
你好,世界!
这几个字符后输出的,却跑到了前面!
使用io.TextIOBase.detach()
Python3的io获得了新的设计,是基于io.TextIOBase
的。io.TextIOBase
有一个detach
功能,可以帮助你获取stdout的buffer。
# vim:fileencoding=utf-8
import sys
msg = "你好,世界!"
buf = sys.stdout.detach()
buf.write(msg.encode('utf-8'))
上面的代码也能够得到正确的结果,可是会有下面的提示:
Exception ignored in: <_io.TextIOWrapper mode='w' encoding='UTF-8'>
ValueError: underlying buffer has been detached
翻阅了一下Python3的文档,是这么说得:After the underlying buffer has been detached, the TextIOBase is in an unusable state. 所以,这并不是一个十分可行的方式。
重新包装stdout
最后一个方式,是使用sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
重新包装一下stdout,一切都OK了。可惜stdout就不是以前那个stdout了,如果你在包装之后的stdout上使用print(sys.stdout.encoding)
,会告诉你encoding属性不存在的。
# vim:fileencoding=utf-8
import sys, codecs
msg = "你好,世界!"
print(sys.stdout.encoding)
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
print(msg)
上面的程序会得到下面的结果:
cp1252
你好,世界!
当然,包装stdout的方式肯定不止一种,下面的也可行:
sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8', buffering=1)
小结
通过这篇文章,我们稍微窥探了一下Python3中io.TextIOBase
的设计。本文涉及的问题,只在Windows平台碰到过,其他平台尚未遇见。
其他参考
(完)