177

我正在编写一个 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 字符(破折号),打印功能在 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. 它解决了这种情况的问题。如果我为使用其他编码(latin-1、cp437、回到 cp1252 等)的系统移植程序,它应该能够识别目标编码。它不是。(例如,当再次使用 IDLE GUI 时,emdash 也会丢失,这在以前没有发生过)
  3. 如果将 emdash 翻译成连字符而不是审讯号,那就更好了。

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

所以,问题:

是否有更好的解决方案使我的代码与输出接口编码无关?

4

6 回答 6

113

我看到了三个解决方案:

  1. 更改输出编码,使其始终输出 UTF-8。请参阅在 Python 中管道 stdout 时设置正确的编码,但我无法让这些示例正常工作。

  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,使用该机制正确编码输出,您可以简单地将 print 替换为myprint必要的地方,而不会使整个代码看起来难看。

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

    http://www.macfreek.nl/memory/Encoding_of_Python_stdout页面很好地总结了如何更改输出编码。尤其是“Stdout 周围的 StreamWriter Wrapper”部分很有趣。本质上,它说要像这样更改 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 中使用,您可以将 '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
    
于 2013-04-20T12:13:34.497 回答
32

根据 Dirk Stöcker 的回答,这里是 Python 3 打印函数的简洁包装函数。就像使用 print 一样使用它。

作为额外的奖励,与其他答案相比,由于最后一个解码步骤,这不会将您的文本打印为字节数组('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')
于 2015-05-01T14:08:47.427 回答
25

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

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

要将 Unicode 打印到 Windows 控制台,您可以使用win-unicode-consolepackage

于 2015-08-24T00:52:02.810 回答
23

我对此进行了更深入的研究,发现最好的解决方案就在这里。

http://blog.notdot.net/2010/07/Getting-unicode-right-in-Python

就我而言,我解决了“UnicodeEncodeError:'charmap'编解码器无法编码字符”

原始代码:

print("Process lines, file_name command_line %s\n"% command_line))

新代码:

print("Process lines, file_name command_line %s\n"% command_line.encode('utf-8'))  
于 2017-05-09T08:03:28.590 回答
14

如果您使用 Windows 命令行打印数据,您应该使用

chcp 65001

这对我有用!

于 2017-05-26T01:19:47.290 回答
2

如果您使用 Python 3.6(可能是 3.5 或更高版本),它不会再给我这个错误。我有一个类似的问题,因为我使用的是 v3.4,但是在我卸载并重新安装后它就消失了。

于 2017-03-01T23:08:17.200 回答