Setting the correct encoding when piping stdout in Python
当通过管道传输Python程序的输出时,Python解释器会对编码感到困惑,并将其设置为"无"。这意味着这样的程序:
1 2 | # -*- coding: utf-8 -*- print u"???" |
正常运行时工作正常,但出现以下故障:
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa0' in position 0: ordinal not in range(128)
在管道顺序中使用时。
管道安装时,最好的方法是什么?我能告诉它使用shell/filesystem/正在使用的任何编码吗?
到目前为止,我看到的建议是直接修改您的site.py,或者使用此hack对默认编码进行硬编码:
1 2 3 4 5 | # -*- coding: utf-8 -*- import sys reload(sys) sys.setdefaultencoding('utf-8') print u"???" |
有没有更好的方法让管道工作?
首先,关于这个解决方案:
1 2 | # -*- coding: utf-8 -*- print u"???".encode('utf-8') |
每次都用给定的编码显式打印是不实际的。这将是重复的和容易出错的。
一个更好的解决方案是在程序开始时更改
1 2 3 | import sys import codecs sys.stdout = codecs.getwriter('utf8')(sys.stdout) |
当在脚本中运行时,代码会工作,因为python将输出编码为终端应用程序使用的任何编码。如果你是管道,你必须自己编码。
经验法则是:在内部始终使用Unicode。解码接收到的内容,并对发送的内容进行编码。
1 2 | # -*- coding: utf-8 -*- print u"???".encode('utf-8') |
另一个教学示例是在iso-8859-1和utf-8之间转换的python程序,使两者之间的所有内容都大写。
1 2 3 4 5 6 7 8 9 10 11 | import sys for line in sys.stdin: # Decode what you receive: line = line.decode('iso8859-1') # Work with Unicode internally: line = line.upper() # Encode what you send: line = line.encode('utf-8') sys.stdout.write(line) |
设置系统默认编码是一个坏主意,因为您使用的某些模块和库可以依赖于它是ASCII的事实。不要这样做。
您可能需要尝试将环境变量"pythonioencoding"更改为"utf_8"。我写了一页关于这个问题的经历。
博客博文博士:
1 2 3 4 5 6 7 | import sys, locale, os print(sys.stdout.encoding) print(sys.stdout.isatty()) print(locale.getpreferredencoding()) print(sys.getfilesystemencoding()) print(os.environ["PYTHONIOENCODING"]) print(chr(246), chr(9786), chr(9787)) |
给你
1 2 3 4 5 6 | utf_8 False ANSI_X3.4-1968 ascii utf_8 ? ? ? |
1 | export PYTHONIOENCODING=utf-8 |
做这个工作,但不能在python本身上设置它…
我们可以做的是验证是否没有设置,并告诉用户在调用脚本之前设置它:
1 2 3 4 | if __name__ == '__main__': if (sys.stdout.encoding is None): print >> sys.stderr,"please set python env PYTHONIOENCODING=UTF-8, example: export PYTHONIOENCODING=UTF-8, when write to stdout." exit(1) |
更新以回复评论:当管道连接到stdout时,问题就存在了。我在Fedora 25 python 2.7.13中测试过
1 2 | python --version Python 2.7.13 |
猫咪
1 2 3 4 5 | #!/usr/bin/env python #-*- coding: utf-8 -*- import sys print sys.stdout.encoding |
跑步/B.Py
1 | UTF-8 |
运行/b.py更少
1 | None |
上周我也遇到过类似的问题。在我的IDE中很容易修复(Pycharm)。
我的解决方法是:
从pycharm菜单栏开始:文件->设置…->编辑器->文件编码,然后将"ide编码"、"项目编码"和"属性文件的默认编码"全部设置为utf-8,她现在的工作方式很有魅力。
希望这有帮助!
克雷格·麦昆答案的可论证的消毒版本。
1 2 3 4 5 6 7 8 9 10 11 | import sys, codecs class EncodedOut: def __init__(self, enc): self.enc = enc self.stdout = sys.stdout def __enter__(self): if sys.stdout.encoding is None: w = codecs.getwriter(self.enc) sys.stdout = w(sys.stdout) def __exit__(self, exc_ty, exc_val, tb): sys.stdout = self.stdout |
用途:
1 2 | with EncodedOut('utf-8'): print u'??????' |
我可以通过调用来"自动化"它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def __fix_io_encoding(last_resort_default='UTF-8'): import sys if [x for x in (sys.stdin,sys.stdout,sys.stderr) if x.encoding is None] : import os defEnc = None if defEnc is None : try: import locale defEnc = locale.getpreferredencoding() except: pass if defEnc is None : try: defEnc = sys.getfilesystemencoding() except: pass if defEnc is None : try: defEnc = sys.stdin.encoding except: pass if defEnc is None : defEnc = last_resort_default os.environ['PYTHONIOENCODING'] = os.environ.get("PYTHONIOENCODING",defEnc) os.execvpe(sys.argv[0],sys.argv,os.environ) __fix_io_encoding() ; del __fix_io_encoding |
是的,如果这个"setenv"失败的话,有可能在这里得到一个无限循环。
我在一个遗留应用程序中遇到了这个问题,很难确定打印内容的位置。我帮自己解决了这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # encoding_utf8.py import codecs import builtins def print_utf8(text, **kwargs): print(str(text).encode('utf-8'), **kwargs) def print_utf8(fn): def print_fn(*args, **kwargs): return fn(str(*args).encode('utf-8'), **kwargs) return print_fn builtins.print = print_utf8(print) |
在我的脚本之上,test.py:
1 2 3 | import encoding_utf8 string = 'Axwell Λ Ingrosso' print(string) |
请注意,这会将所有要打印的调用更改为使用编码,因此控制台将打印此内容:
1 2 | $ python test.py b'Axwell \xce\x9b Ingrosso' |
我只是想在这里提一件事,在我最终意识到发生了什么之前,我必须花很长时间来试验。这对这里的每个人来说都是显而易见的,以至于他们没有费心提及它。但如果他们有,我会有所帮助的,所以根据这个原则…!
注意:我特别使用Jython,v 2.7,所以这可能不适用于CPython…
NB2:my.py文件的前两行是:
1 2 | # -*- coding: utf-8 -*- from __future__ import print_function |
"%"(又称"插值运算符")字符串构造机制也会导致其他问题…如果"环境"的默认编码是ASCII,并且您尝试执行类似的操作
1 | print("bonjour, %s" %"fréd" ) # Call this"print A" |
在Eclipse中运行不会有困难…在Windows CLI(DOS窗口)中,您会发现编码是代码页850(我的Windows 7操作系统)或类似的内容,至少可以处理欧洲重音字符,因此它可以工作。
1 | print( u"bonjour, %s" %"fréd" ) # Call this"print B" |
也会起作用。
如果您从cli直接指向一个文件,stdout编码将为none,这将默认为ascii(在我的操作系统上),它将无法处理上述任何一个打印…(可怕的编码错误)。
所以你可以考虑通过使用
1 | sys.stdout = codecs.getwriter('utf8')(sys.stdout) |
尝试在cli管道中运行到文件…很奇怪的是,上面的打印A会起作用…但上面的打印B会抛出编码错误!但是,以下功能正常:
1 | print( u"bonjour," +"fréd" ) # Call this"print C" |
我得出的结论(暂时)是,如果使用"U"前缀指定为Unicode字符串的字符串被提交到%-处理机制,那么它似乎涉及使用默认环境编码,而不管您是否已将stdout设置为重定向!
人们如何处理这是一个选择。我欢迎一位Unicode专家来解释为什么会发生这种情况,我是否在某种程度上弄错了它,这是什么首选的解决方案,它是否也适用于cpython,它是否在python 3中发生,等等。
在Ubuntu12.10和GNOME终端上,当程序打印到stdout或连接到其他程序的管道时,不会产生任何错误。文件编码和终端编码都是UTF-8。
1 2 3 4 5 6 7 | $ cat a.py # -*- coding: utf-8 -*- print"???" $ python a.py ??? $ python a.py | tee out ??? |
您使用的是什么操作系统和终端模拟器?我听说我的一些同事在使用iterm 2时也有类似的问题,而os x;iterm 2可能是罪魁祸首。
更新:此答案错误-有关详细信息,请参阅注释