UnicodeEncodeError:“charmap”编解码器无法编码-字符映射到<undefined>,打印功能

2024-04-20 09:35:30 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在编写一个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 GUI并改用Windows控制台之前,一切似乎都正常。返回的页面有一个U+2014字符(em破折号),打印功能在Windows GUI(我认为是代码页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'))

现在它用?替换有问题的字符“-”。不是最理想的情况(连字符应该是更好的替代品),但对我来说已经足够好了。

有几件事我不喜欢从我的解决方案。

  1. 这些代码在解码、编码和解码时都很难看。
  2. 它解决了这个问题。如果我使用其他编码(拉丁语-1、cp437、返回cp1252等)为系统移植程序,它应该能够识别目标编码。不是的。(例如,当再次使用空闲的GUI时,emdash也会丢失,这是以前没有的)
  3. 如果将emdash翻译成连字符而不是询问bang会更好。

问题不在于emdash(我可以想出几种方法来解决这个特别的问题),但我需要编写健壮的代码。我正在向页面提供来自数据库的数据,这些数据可以返回。我可以预见许多其他冲突的情况:“Á”U+00c1(在我的数据库中是可能的)可以翻译成CP-850(西欧语言的DOS/Windows控制台编码),但不能翻译成CP-437(美国英语的编码,在许多Windows安装中是默认的)。

所以,问题是:

有没有更好的解决方案,使我的代码不可知从输出接口编码?


Tags: 数据方法代码程序编码dataresponsewindows
3条回答

我看到了三种解决方案:

  1. 更改输出编码,使其始终输出UTF-8。请参见例如Setting the correct encoding when piping stdout in Python,但我无法使这些示例起作用。

  2. 下面的示例代码使输出知道目标字符集。

    # -*- 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,使用该机制正确地编码输出,您可以在任何需要的地方用myprint替换打印,而不必使整个代码看起来很难看。

  3. 在软件开始时全局重置输出编码:

    页面http://www.macfreek.nl/memory/Encoding_of_Python_stdout很好地总结了如何更改输出编码。特别是“StreamWriter包装Stdout”一节很有意思。实际上,它说要像这样更改I/O编码函数:

    在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中使用,则可以用“xmlcharrefreplace”替换“strict”,以获取不可打印字符的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
    

出于调试目的,可以使用print(repr(data))

要显示文本,请始终打印Unicode。不要在脚本中硬编码环境的字符编码,例如Cp850。要解码HTTP响应,请参见A good way to get the charset/encoding of an HTTP response in Python

要将Unicode打印到Windows控制台,可以use ^{} package

基于Dirk Stócker的答案,这里有一个用于Python3打印函数的简洁包装函数。使用它就像使用打印一样。

作为额外的好处,与其他答案相比,这不会将文本打印为bytearray('b“content“),而是作为普通字符串('content'),因为最后一个解码步骤。

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')

相关问题 更多 >