我以为我知道编码和Python的一切,但今天我遇到了一个奇怪的问题:尽pipe控制台设置为代码页850 – 而Python正确报告 – 我放在命令行上的参数似乎编码在代码页1252如果我尝试用sys.stdin.encoding解码,我得到了错误的结果。 如果我假设'cp1252',忽略sys.stdout.encoding报告,它工作。
我错过了什么,或者这是Python中的错误? Windows? 注意:我在Windows 7 EN上运行Python 2.6.6,区域设置为法语(瑞士)。
在下面的testing程序中,我检查文字是否正确解释并可以打印 – 这是有效的。 但是我在命令行传递的所有值似乎都被错误地编码了:
#!/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 ""
在新创build的控制台中运行
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 ]
而我期望第四个字符的序数值是130而不是233(参见代码页850和1252 )。
注意:欧元符号128的值是一个谜 – 因为cp850没有它。 否则,'?' 预计 – cp850不能打印的字符,我已经在转换使用“replace”。
如果我通过发出chcp 1252
将控制台的代码页更改为chcp 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.stdintesting。 这是按预期工作的:在cp850中,input“é”的结果是一个序数值130.所以这个问题实际上只针对命令行。 那么,命令行的处理与标准input不同吗?
编辑2:似乎我有错误的关键字。 我在SO上发现了另一个非常紧密的话题: 在Windows上,从Python 2.x的命令行参数中读取Unicode字符 。 不过,如果命令行不像sys.stdin那样编码,并且由于sys.getdefaultencoding()报告“ascii”,似乎无法知道它的实际编码。 我find了使用win32扩展hacky的答案。
回复自己:
在Windows上,控制台使用的编码(即sys.stdin / out的编码)不同于各种操作系统提供的字符串的编码 – 通过例如os.getenv(),sys.argv获得,当然还有更多。
由sys.getdefaultencoding()提供的编码实际上是 – Python开发者选择的缺省值,用于匹配解释器在极端情况下使用的“最合理的编码”。 我在我的Python 2.6上得到了“ascii”,并用可移植的Python 3.1进行了尝试,结果产生了'utf-8'。 两者都不是我们正在寻找的 – 他们只是编码转换功能的后备。
正如本页似乎所述,OS提供的字符串使用的编码由活动代码页(ACP)控制。 由于Python没有一个本地函数来检索它,我不得不使用ctypes:
from ctypes import cdll os_encoding = 'cp' + str(cdll.kernel32.GetACP())
编辑:但是正如Jacek所说,实际上有一个更强大的Pythonic方法来做到这一点( 语义需要验证,但直到证明错误,我会用这个)
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”要好一些。
这是一个不幸的事情,虽然我觉得它比这个ActiveState代码食谱 (通过在我的问题的编辑2中提到的SO问题链接)的侵入性少一点。 我在这里看到的优点是,这可以应用到os.getenv(),而不是只适用于sys.argv。
我尝试了解决方案。 它可能仍然有一些编码问题。 我们需要使用真正的字体。 固定:
这是我对编码错误的完整修复:
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))
注意 :执行程序时可以看到字体改变。