在python中管道stdout时设置正确的编码

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')

每次都用给定的编码显式打印是不实际的。这将是重复的和容易出错的。

一个更好的解决方案是在程序开始时更改sys.stdout,以使用选定的编码进行编码。下面是我在python上找到的一个解决方案:如何选择sys.stdout.encoding?特别是"Toka"的评论:

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可能是罪魁祸首。

更新:此答案错误-有关详细信息,请参阅注释