我以为我对编码和 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(请参阅代码页 850 和 1252 )。
注意:欧元符号 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 扩展的答案很老套。
原文由 user948581 发布,翻译遵循 CC BY-SA 4.0 许可协议
回复我自己:
在 Windows 上,控制台使用的编码(因此,sys.stdin/out 的编码)不同于操作系统提供的各种字符串的编码——通过 os.getenv()、sys.argv 等获得。
sys.getdefaultencoding() 提供的编码实际上是一个默认值,由 Python 开发人员选择以匹配解释器在极端情况下使用的“最合理编码”。我在我的 Python 2.6 上得到“ascii”,并尝试使用可移植的 Python 3.1,它产生“utf-8”。两者都不是我们要找的——它们只是编码转换函数的后备。
正 如此页面 所言,操作系统提供的字符串使用的编码由活动代码页 (ACP) 管理。由于 Python 没有用于检索它的本机函数,因此我不得不使用 ctypes:
编辑: 但正如 Jacek 所建议的那样,实际上有一种更健壮和 Pythonic 的方法来做到这一点( 语义 需要验证,但在被证明错误之前,我会使用它)
然后
在我的系统上,
os_encoding = 'cp1252'
,所以它有效。我很确定这会在其他平台上崩溃,所以请随意编辑并使其更通用。我们肯定需要某种 Windows 报告的 ACP 和 Python 编码名称之间的转换表——这比仅仅在前面加上“cp”要好。不幸的是,这是一个 hack,尽管我发现它比 这个 ActiveState Code Recipe (与我的问题的编辑 2 中提到的 SO 问题相关联)建议的侵入性要小一些。我在这里看到的优势是它可以应用于 os.getenv(),而不仅仅是 sys.argv。