【问题标题】:Python, windows console and encodings (cp 850 vs cp1252)Python、Windows 控制台和编码(cp 850 与 cp1252)
【发布时间】:2012-02-10 10:45:48
【问题描述】:

我以为我对编码和 Python 了如指掌,但今天我遇到了一个奇怪的问题:虽然控制台设置为代码页 850 - Python 正确报告了它 - 我放在命令行上的参数似乎被编码为代码页 1252。如果我尝试使用 sys.stdin.encoding 对其进行解码,则会得到错误的结果。如果我假设“cp1252”,忽略 sys.stdout.encoding 报告的内容,它会起作用。

我是否遗漏了什么,或者这是 Python 中的一个错误?窗户?注意:我在 Windows 7 EN 上运行 Python 2.6.6,语言环境设置为法语(瑞士)。

在下面的测试程序中,我检查了文字是否被正确解释并且可以打印 - 这很有效。但是我在命令行上传递的所有值似乎都被错误地编码了:

#!/usr/bin/python
# -*- encoding: utf-8 -*-
import sys

literal_mb = 'utf-8 literal:   üèéÃÂç€ÈÚ'
literal_u = u'unicode literal: üèéÃÂç€ÈÚ'
print "Testing literals"
print literal_mb.decode('utf-8').encode(sys.stdout.encoding,'replace')
print literal_u.encode(sys.stdout.encoding,'replace')

print "Testing arguments ( stdin/out encodings:",sys.stdin.encoding,"/",sys.stdout.encoding,")"
for i in range(1,len(sys.argv)):
    arg = sys.argv[i]
    print "arg",i,":",arg
    for ch in arg:
        print "  ",ch,"->",ord(ch),
        if ord(ch)>=128 and sys.stdin.encoding == 'cp850':
            print "<-",ch.decode('cp1252').encode(sys.stdout.encoding,'replace'),"[assuming input was actually cp1252 ]"
        else:
            print ""

在新创建的控制台中,运行时

C:\dev>test-encoding.py abcé€

我得到以下输出

Testing literals
utf-8 literal:   üèéÃÂç?ÈÚ
unicode literal: üèéÃÂç?ÈÚ
Testing arguments ( stdin/out encodings: cp850 / cp850 )
arg 1 : abcÚÇ
   a -> 97
   b -> 98
   c -> 99
   Ú -> 233 <- é [assuming input was actually cp1252 ]
   Ç -> 128 <- ? [assuming input was actually cp1252 ]

虽然我希望第 4 个字符的序数值为 130 而不是 233(请参阅代码页 8501252)。

注意:欧元符号的值 128 是个谜 - 因为 cp850 没有它。否则,'?'预期 - cp850 无法打印字符,我在转换中使用了“替换”。

如果我通过发出 chcp 1252 将控制台的代码页更改为 1252 并运行相同的命令,我(正确)获得

Testing literals
utf-8 literal:   üèéÃÂç€ÈÚ
unicode literal: üèéÃÂç€ÈÚ
Testing arguments ( stdin/out encodings: cp1252 / cp1252 )
arg 1 : abcé€
   a -> 97
   b -> 98
   c -> 99
   é -> 233
   € -> 128

任何想法我错过了什么?

编辑 1: 我刚刚通过阅读 sys.stdin 进行了测试。这按预期工作:在 cp850 中,键入 'é' 会导致序数值为 130。所以问题实际上只出在命令行上。那么,命令行与标准输入的处理方式不同吗?

编辑 2: 看来我的关键字有误。我在 SO 上找到了另一个非常接近的话题:Read Unicode characters from command-line arguments in Python 2.x on Windows。尽管如此,如果命令行没有像 sys.stdin 那样编码,并且由于 sys.getdefaultencoding() 报告“ascii”,似乎没有办法知道它的实际编码。我发现使用 win32 扩展的答案非常 hacky。

【问题讨论】:

    标签: python windows encoding


    【解决方案1】:

    回复我自己:

    在 Windows 上,控制台使用的编码(因此是 sys.stdin/out 的编码)与操作系统提供的各种字符串的编码不同 - 例如通过os.getenv()、sys.argv,当然还有更多。

    sys.getdefaultencoding() 提供的编码实际上是一个默认值,由 Python 开发人员选择,以匹配解释器在极端情况下使用的“最合理的编码”。我在我的 Python 2.6 上得到了“ascii”,并尝试了可移植的 Python 3.1,它产生了“utf-8”。两者都不是我们想要的——它们只是编码转换函数的后备。

    正如this page 所说,操作系统提供的字符串使用的编码由活动代码页 (ACP) 管理。由于 Python 没有本地函数来检索它,我不得不使用 ctypes:

    from ctypes import cdll
    os_encoding = 'cp' + str(cdll.kernel32.GetACP())
    

    编辑:但正如 Jacek 所建议的那样,实际上有一种更健壮和 Pythonic 的方式来做到这一点(semantics 需要验证,但在证明错误之前,我会使用它)

    import locale
    os_encoding = locale.getpreferredencoding()
    # This returns 'cp1252' on my system, yay!
    

    然后

    u_argv = [x.decode(os_encoding) for x in sys.argv]
    u_env = os.getenv('myvar').decode(os_encoding)
    

    在我的系统上,os_encoding = 'cp1252',所以它可以工作。我很确定这会在其他平台上中断,所以请随意编辑并使其更通用。我们当然需要在 Windows 报告的 ACP 和 Python 编码名称之间建立某种转换表——这比仅仅在前面加上 'cp' 更好。

    不幸的是,这是一个 hack,尽管我发现它比this ActiveState Code Recipe 建议的侵入性要小一些(由我的问题的编辑 2 中提到的 SO 问题链接)。我在这里看到的优点是这可以应用于 os.getenv(),而不仅仅是 sys.argv。

    【讨论】:

    • 对于 Linux,通常locale.getpreferredencoding() 或在使用locale.setlocale() 之后 - locale.getlocale()[1] 为控制台和环境访问提供正确的编码。不过,对于大多数现代系统来说,硬编码的 UTF-8 通常就足够了(因此它是最好的后备值)。
    【解决方案2】:

    我尝试了解决方案。它可能仍然存在一些编码问题。我们需要使用真正的字体。 修复:

    1. 在 cmd 中运行 chcp 65001 将编码更改为 UTF-8。
    2. 将 cmd 字体更改为 True-Type 字体,例如支持 65001 之前的代码页

    这是我对编码错误的完整修复:

    def fixCodePage():
        import sys
        import codecs
        import ctypes
        if sys.platform == 'win32':
            if sys.stdout.encoding != 'cp65001':
                os.system("echo off")
                os.system("chcp 65001") # Change active page code
                sys.stdout.write("\x1b[A") # Removes the output of chcp command
                sys.stdout.flush()
            LF_FACESIZE = 32
            STD_OUTPUT_HANDLE = -11
            class COORD(ctypes.Structure):
            _fields_ = [("X", ctypes.c_short), ("Y", ctypes.c_short)]
    
            class CONSOLE_FONT_INFOEX(ctypes.Structure):
                _fields_ = [("cbSize", ctypes.c_ulong),
                ("nFont", ctypes.c_ulong),
                ("dwFontSize", COORD),
                ("FontFamily", ctypes.c_uint),
                ("FontWeight", ctypes.c_uint),
                ("FaceName", ctypes.c_wchar * LF_FACESIZE)]
    
            font = CONSOLE_FONT_INFOEX()
            font.cbSize = ctypes.sizeof(CONSOLE_FONT_INFOEX)
            font.nFont = 12
            font.dwFontSize.X = 7
            font.dwFontSize.Y = 12
            font.FontFamily = 54
            font.FontWeight = 400
            font.FaceName = "Lucida Console"
            handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
            ctypes.windll.kernel32.SetCurrentConsoleFontEx(handle, ctypes.c_long(False), ctypes.pointer(font))
    

    注意:您可以在执行程序时看到字体变化。

    【讨论】:

    • 您可以删除chcp 的输出,只需使用os.system('chcp 65001 &gt;nul')
    猜你喜欢
    • 2018-08-13
    • 2014-12-30
    • 1970-01-01
    • 2010-12-20
    • 2013-06-28
    • 1970-01-01
    • 2010-12-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多