在Python中管道输出标准输出时设置正确的编码


343

当传递Python程序的输出的管道时,Python解释器会对编码感到困惑,并将其设置为None。这意味着这样的程序:

# -*- coding: utf-8 -*-
print u"åäö"

正常运行时可以正常工作,但失败:

UnicodeEncodeError:'ascii'编解码器无法在位置0编码字符u'\ xa0':序数不在范围内(128)

以管道顺序使用时。

使管道工作的最佳方法是什么?我能告诉它使用外壳程序/文件系统/正在使用的任何编码吗?

到目前为止,我所看到的建议是直接修改site.py,或使用此hack硬编码defaultencoding:

# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
print u"åäö"

有没有更好的方法可以使管道工作?



2
如果您在Windows上遇到此问题,也可以chcp 65001在执行脚本之前运行。这可能会有问题,但通常会有所帮助,并且不需要太多的输入(少于set PYTHONIOENCODING=utf_8)。
Tomasz Gandor

chcp命令与设置PYTHONIOENCODING不同。我认为chcp只是终端本身的配置,与写入文件无关(这是在管道输出stdout时所做的事情)。setx PYTHONENCODING utf-8如果要保存键入,请尝试使其永久。
ejm


我面对一个有点相关的问题,并在这里找到了解决办法- > stackoverflow.com/questions/48782529/...
bkrishna2006

Answers:


162

您的代码在脚本中运行时有效,因为Python将输出编码为您的终端应用程序正在使用的任何编码。如果要进行管道传输,则必须自己对其进行编码。

经验法则是:始终在内部使用Unicode。解码收到的内容,并对发送的内容进行编码。

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

另一个教学示例是一个Python程序,用于在ISO-8859-1和UTF-8之间进行转换,从而使两者之间的所有内容均大写。

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的事实。不要这样


11
问题是用户不想明确指定编码。他只想将Unicode用于IO。并且他使用的编码应该是在语言环境设置中指定的编码,而不是在终端应用程序设置中指定的编码。AFAIK,在这种情况下,Python 3使用语言环境编码。改变sys.stdout似乎是一种更愉快的方式。
Andrey Vlasovskikh 2010年

4
当一个编码或解码调用丢失或一次添加到某个地方时,明确地对每个字符串进行编码/解码必然会导致错误。当输出是终端时可以设置输出编码,因此当输出不是终端时可以设置输出编码。甚至有一个标准的LC_CTYPE环境来指定它。这是一个但不支持python的方法。
拉斯穆斯·卡伊

65
这个答案是错误的。您应该在程序的每个输入和输出上进行手动转换;那是脆弱的,完全无法维持。
格伦·梅纳德

29
@Glenn Maynard:那么IYO正确的答案是什么?告诉我们比说“这个答案是错误的”
smci 2012年

14
@smci:答案是不修改脚本,设置PYTHONIOENCODING如果要重定向脚本的stdout在Python 2
JFS

168

首先,关于此解决方案:

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

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

更好的解决方案是sys.stdout在程序开始时进行更改,以使用选定的编码进行编码。这是我在Python上找到的一种解决方案:如何选择sys.stdout.encoding?,特别是“ toka”的评论:

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)

7
不幸的是,将sys.stdout更改为仅接受unicode会破坏许多期望它接受已编码字节串的库。
nosklo

6
nosklo:那么当输出是终端时,它如何可靠,自动地工作?
Rasmus Kaj

3
@Rasmus Kaj:只需定义自己的unicode打印功能,并在每次要打印unicode时使用它:def myprint(unicodeobj): print unicodeobj.encode('utf-8')-您可以通过检查自动检测终端编码sys.stdout.encoding,但应考虑实际情况None(例如,将输出重定向到文件时)因此无论如何您都需要一个单独的功能。
2010年

3
@nosklo:这不会使sys.stdout仅接受Unicode。您可以将str和unicode都传递给StreamWriter。
Glenn Maynard '04

9
我认为这个答案是针对python2的。 在旨在同时支持python2和python3的代码上请谨慎使用。对我来说,当在python3下运行时,这是一件令人头疼的事情。
2016年

130

您可能需要尝试将环境变量“ PYTHONIOENCODING”更改为“ utf_8”。我写了一篇关于这个问题的磨难页面

博客文章的Tl; dr:

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

给你

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻

2
更改sys.stdout.encoding也许不起作用,但改变sys.stdout的不工作:sys.stdout = codecs.getwriter(encoding)(sys.stdout)。可以在python程序中完成此操作,因此不会强迫用户设置env变量。
blueFast 2013年

7
@ jeckyll2hide:PYTHONIOENCODING有效。用户环境定义字节如何解释为文本。您的脚本不应假设并指示用户环境使用哪种字符编码。如果Python没有自动获取设置,PYTHONIOENCODING则可以为您的脚本设置。除非将输出重定向到文件/管道,否则您不需要它。
jfs

8
+1。老实说,我认为这是一个Python错误。当我重定向输出时,我想要那些在终端上但在文件中的相同字节。也许不是每个人都适合,但这是一个很好的默认设置。严重崩溃而没有解释通常“正常运行”的琐碎操作是一个糟糕的默认设置。
SnakE

@SnakE:我可以合理地解释为什么Python的实现有意在启动时在stdout上强制采用铁定和永久的编码选择的唯一方法,可能是为了防止以后出现任何编码错误的东西。或更改它只是未实现的功能,在这种情况下,允许用户稍后对其进行更改将是合理的Python功能请求。
daveagp 2015年

2
@daveagp我的意思是,程序的行为不应取决于是否重定向,除非我真的想要它,在这种情况下,我自己实现它。Python的行为违背了我使用其他任何控制台工具的经验。这违反了最不令人惊讶的原则。我认为这是一个设计缺陷,除非有很强的理由。
SnakE

62
export PYTHONIOENCODING=utf-8

做这项工作,但不能在python本身上设置它...

我们可以做的是验证是否未设置,并在调用脚本之前通过以下命令告诉用户进行设置:

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中进行了测试

python --version
Python 2.7.13

猫b.py

#!/usr/bin/env python
#-*- coding: utf-8 -*-
import sys

print sys.stdout.encoding

运行./b.py

UTF-8

运行./b.py | 减

None

2
该检查在Python 2.7.13中不起作用。sys.stdout.encoding是根据LC_CTYPE语言环境值。
amphetamachine

1
mail.python.org/pipermail/python-list/2011-June/605938.html存在的例子仍然工作,即当您使用./a.py> out.txt sys.stdout.encoding是没有的
塞尔吉奥

我在Backblaze B2中使用同步脚本遇到了类似的问题,并且导出PYTHONIOENCODING = utf-8解决了我的问题。Debian Stretch上的Python 2.7。
0x3333

5

上周有一个类似的问题。在我的IDE(PyCharm)中很容易修复。

这是我的解决方法:

从PyCharm菜单栏开始:文件->设置...->编辑器->文件编码,然后将:“ IDE编码”,“项目编码”和“属性文件的默认编码”全部设置为UTF-8,她现在可以工作了像个魅力。

希望这可以帮助!


4

克雷格·麦昆(Craig McQueen)的答案可能是经过消毒的版本。

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

用法:

with EncodedOut('utf-8'):
    print u'ÅÄÖåäö'

2

我可以通过以下方式“自动化”它:

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
有趣,但是烟斗对此并不满意
n611x007 2012年

2

我只是以为我在这里提到了一些东西,在我最终意识到发生了什么之前,我不得不花很长时间进行试验。对于这里的每个人来说,这可能是如此明显,以至于他们都没有理会它。但是如果他们有的话,这对我会有所帮助,所以按照这个原则...!

注意:我专门使用的是Jython 2.7版,所以可能这可能不适用于CPython ...

NB2:我的.py文件的前两行是:

# -*- coding: utf-8 -*-
from __future__ import print_function

“%”(也称为“插值运算符”)字符串构造机制也会引起其他问题……如果“环境”的默认编码为ASCII,则尝试执行类似的操作

print( "bonjour, %s" % "fréd" )  # Call this "print A"

您将在Eclipse中运行没有困难...在Windows CLI(DOS窗口)中,您会发现编码是代码页850(我的Windows 7 OS)或类似的东西,至少可以处理欧洲带有重音符号的字符,因此它会工作的。

print( u"bonjour, %s" % "fréd" ) # Call this "print B"

也可以。

如果是OTOH,您从CLI定向到文件,则stdout编码将为None,它将默认设置为ASCII(无论如何在我的OS上),它将无法处理以上任何打印...(可怕的编码)错误)。

因此,您可能会考虑使用来重定向您的标准输出

sys.stdout = codecs.getwriter('utf8')(sys.stdout)

并尝试在CLI管道中运行到文件...很奇怪,上面的打印A可以工作...但是上面的打印B将抛出编码错误!但是,以下内容可以正常运行:

print( u"bonjour, " + "fréd" ) # Call this "print C"

我得出的结论(临时)是,如果将使用“ u”前缀指定为Unicode字符串的字符串提交给%-handling机制,则似乎涉及使用默认环境编码,无论是否已将stdout设置为重定向!

人们如何处理这是一个选择问题。我欢迎Unicode专家说出为什么会发生这种情况,我是否以某种方式出错了,对此的首选解决方案,是否也适用于CPython,它是否发生在Python 3中,等等。


这并不奇怪,这是因为"fréd"它是字节序列而不是Unicode字符串,因此codecs.getwriter包装器将不理会它。您需要领导u,或者from __future__ import unicode_literals
Matthias Urlichs 2014年

@MatthiasUrlichs好吧...谢谢...但是我只是发现编码是IT最令人毛骨悚然的方面之一。您从哪里得到理解?例如,我刚刚在这里发布了另一个有关编码的问题:stackoverflow.com/questions/44483067/…:这是关于Java,Eclipse,Cygwin和Gradle的。如果您的专业知识走到这一步,请提供帮助...首先,我想知道在哪里可以了解更多信息!
麦克罗丹(Mike

1

我在旧版应用程序中遇到了这个问题,很难确定打印的内容。我帮助自己解决了这个问题:

# 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:

import encoding_utf8
string = 'Axwell Λ Ingrosso'
print(string)

请注意,这会将所有调用更改为使用编码进行打印,因此您的控制台将打印以下内容:

$ python test.py
b'Axwell \xce\x9b Ingrosso'

1

在Windows上,当从编辑器(例如Sublime Text)运行Python代码时,我经常遇到此问题,但没有从命令行运行它时。

在这种情况下,请检查编辑器的参数。对于SublimeText,这Python.sublime-build解决了它:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.