UnicodeEncodeError: 'charmap' 编码器无法编码 - 字符映射到<未定义>, 打印函数
我正在写一个Python(Python 3.3)程序,目的是通过POST方法向网页发送一些数据。为了调试,我会获取网页的结果,并使用print()
函数把它显示在屏幕上。
我的代码大致是这样的:
conn.request("POST", resource, params, headers)
response = conn.getresponse()
print(response.status, response.reason)
data = response.read()
print(data.decode('utf-8'));
这里的HTTPResponse
的.read()
方法会返回一个bytes
类型的数据,这个数据是网页的内容(它是一个格式良好的UTF-8文档)。在我使用Windows的IDLE图形界面时,这一切看起来都没问题,但当我换成Windows控制台时,就出现了问题。返回的网页中有一个U+2014字符(即长破折号),在Windows GUI中,print()
函数能很好地显示它(我猜是使用了1252编码),但在Windows控制台中(使用850编码)就显示不正常了。由于默认的strict
行为,我遇到了以下错误:
UnicodeEncodeError: 'charmap' codec can't encode character '\u2014' in position 10248: character maps to <undefined>
我用一段看起来不太优雅的代码解决了这个问题:
print(data.decode('utf-8').encode('cp850','replace').decode('cp850'))
现在它把那个有问题的字符“—”替换成了?
。虽然这不是最理想的解决方案(用连字符替换可能更好),但对我来说已经足够用了。
不过,我对这个解决方案有几个不满意的地方。
- 代码看起来很乱,涉及到很多解码、编码和再解码。
- 这个解决方案只适用于当前的情况。如果我把程序移植到其他编码的系统(比如latin-1、cp437、再回到cp1252等),它就无法识别目标编码了。(例如,当我再次使用IDLE GUI时,长破折号也会消失,这在之前是不会发生的)
- 如果长破折号能被翻译成连字符,而不是问号,那就更好了。
问题不在于长破折号(我可以想到几种方法来解决这个特定的问题),而是我需要写出更健壮的代码。我从数据库中获取数据并发送到网页,这些数据可能会返回。我可以预见到许多其他可能出现冲突的情况:比如一个'Á'(U+00c1,可能在我的数据库中存在)在CP-850(适用于西欧语言的DOS/Windows控制台编码)中能正常显示,但在CP-437(美国英语的编码,许多Windows安装的默认编码)中就不行了。
所以,我的问题是:
有没有更好的解决方案,让我的代码不受输出界面编码的影响?
6 个回答
为了调试,你可以使用 print(repr(data))
来查看数据的详细信息。
在显示文本时,记得要打印Unicode格式的内容。不要在你的代码里直接写死你环境的字符编码,比如 Cp850。如果你想解码HTTP响应,可以参考这个链接:在Python中获取HTTP响应的字符集/编码的好方法。
如果你想在Windows控制台打印Unicode,可以使用 win-unicode-console
这个包。
根据Dirk Stöcker的回答,这里有一个很方便的包装函数,可以用在Python 3的print函数上。你可以像使用print一样使用它。
另外,相比其他的回答,这个函数不会把你的文本打印成字节数组(像这样'b"内容"),而是会以普通字符串的形式打印(像这样'内容'),这是因为最后有一个解码的步骤。
def uprint(*objects, sep=' ', end='\n', file=sys.stdout):
enc = file.encoding
if enc == 'UTF-8':
print(*objects, sep=sep, end=end, file=file)
else:
f = lambda obj: str(obj).encode(enc, errors='backslashreplace').decode(enc)
print(*map(f, objects), sep=sep, end=end, file=file)
uprint('foo')
uprint(u'Antonín Dvořák')
uprint('foo', 'bar', u'Antonín Dvořák')
我看到有三种解决方法:
改变输出编码,这样它就会始终输出UTF-8格式。你可以参考一下这个链接:在Python中设置正确的编码,不过我自己试过这些例子,没能成功。
下面的示例代码可以让输出适应你想要的字符集。
# -*- coding: utf-8 -*- import sys print sys.stdout.encoding print u"Stöcker".encode(sys.stdout.encoding, errors='replace') print u"Стоескер".encode(sys.stdout.encoding, errors='replace')
这个例子会把我名字里任何不可打印的字符替换成问号。
如果你创建一个自定义的打印函数,比如叫
myprint
,用这种方法来正确编码输出,你就可以在需要的地方把print替换成myprint
,这样代码看起来就不会那么乱了。在软件开始时全局重置输出编码:
这个页面http://www.macfreek.nl/memory/Encoding_of_Python_stdout对如何改变输出编码有个很好的总结。特别是“Stdout的StreamWriter包装器”这一部分很有意思。基本上它的意思是像这样改变输入输出的编码函数:
在Python 2中:
if sys.stdout.encoding != 'cp850': sys.stdout = codecs.getwriter('cp850')(sys.stdout, 'strict') if sys.stderr.encoding != 'cp850': sys.stderr = codecs.getwriter('cp850')(sys.stderr, 'strict')
在Python 3中:
if sys.stdout.encoding != 'cp850': sys.stdout = codecs.getwriter('cp850')(sys.stdout.buffer, 'strict') if sys.stderr.encoding != 'cp850': sys.stderr = codecs.getwriter('cp850')(sys.stderr.buffer, 'strict')
如果在CGI中输出HTML,你可以把'strict'替换成'xmlcharrefreplace',这样就能为不可打印字符生成HTML编码的标签。
你可以随意修改这些方法,设置不同的编码等等……不过要注意,输出不指定的数据仍然是行不通的。所以任何数据、输入、文本都必须能够正确转换成unicode:
# -*- coding: utf-8 -*- import sys import codecs sys.stdout = codecs.getwriter("iso-8859-1")(sys.stdout, 'xmlcharrefreplace') print u"Stöcker" # works print "Stöcker".decode("utf-8") # works print "Stöcker" # fails