我以为我知道关于编码和Python的一切,但今天我遇到了一个奇怪的问题:虽然控制台设置为代码页850 - 并且Python正确报告 - 我在命令行上放置的参数似乎在代码页1252中编码如果我尝试用sys.stdin.encoding解码它们,我得到错误的结果.如果我假设'cp1252',忽略sys.stdout.encoding报告的内容,它就可以了.
我错过了什么,或者这是Python中的错误?Windows?注意:我在Windows 7 EN上运行Python 2.6.6,locale设置为French(瑞士).
在下面的测试程序中,我检查文字是否正确解释并可以打印 - 这是有效的.但是我在命令行上传递的所有值似乎都被错误地编码:
#!/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 ""
Run Code Online (Sandbox Code Playgroud)
在新创建的控制台中,运行时
C:\dev>test-encoding.py abcé€
Run Code Online (Sandbox Code Playgroud)
我得到以下输出
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 ]
Run Code Online (Sandbox Code Playgroud)
虽然我希望第四个字符的序数值为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
Run Code Online (Sandbox Code Playgroud)
我缺少什么想法?
编辑1:我刚刚通过阅读sys.stdin进行了测试.这按预期工作:在cp850中,键入"é"会导致序数值为130.所以问题实际上只适用于命令行.那么,命令行的处理方式是否与标准输入不同?
编辑2:我似乎错了关键字.我在SO上找到了另一个非常接近的主题:在Windows上的Python 2.x中从命令行参数中读取Unicode字符.但是,如果命令行没有像sys.stdin那样编码,并且由于sys.getdefaultencoding()报告'ascii',似乎无法知道它的实际编码.我发现使用win32扩展的答案非常hacky.
小智 24
回复自己:
在Windows上,控制台使用的编码(因此,sys.stdin/out的编码)与各种OS提供的字符串的编码不同 - 通过例如os.getenv(),sys.argv获得,当然还有更多.
sys.getdefaultencoding()提供的编码实际上是 - 默认情况下,由Python开发人员选择,以匹配解释器在极端情况下使用的"最合理的编码".我在我的Python 2.6上得到'ascii',并尝试使用便携式Python 3.1,它产生'utf-8'.两者都不是我们想要的 - 它们只是编码转换函数的后备.
由于此页面似乎已声明,操作系统提供的字符串使用的编码由活动代码页(ACP)控制.由于Python没有本机函数来检索它,我不得不使用ctypes:
from ctypes import cdll
os_encoding = 'cp' + str(cdll.kernel32.GetACP())
Run Code Online (Sandbox Code Playgroud)
编辑:但正如Jacek所说,实际上有更强大的Pythonic方法(语义需要验证,但在证明错误之前,我会使用它)
import locale
os_encoding = locale.getpreferredencoding()
# This returns 'cp1252' on my system, yay!
Run Code Online (Sandbox Code Playgroud)
然后
u_argv = [x.decode(os_encoding) for x in sys.argv]
u_env = os.getenv('myvar').decode(os_encoding)
Run Code Online (Sandbox Code Playgroud)
在我的系统上,os_encoding = 'cp1252'所以它的工作原理.我很确定这会在其他平台上中断,所以请随意编辑并使其更通用.我们肯定需要在Windows报告的ACP和Python编码名称之间使用某种转换表 - 这比仅仅预先添加'cp'更好.
不幸的是,这是一个黑客攻击,虽然我发现它比这个ActiveState代码配方建议的更具侵入性(与我的问题的编辑2中提到的SO问题相关联).我在这里看到的优点是,这可以应用于os.getenv(),而不仅仅适用于sys.argv.
| 归档时间: |
|
| 查看次数: |
19113 次 |
| 最近记录: |