Python中stderr的默认编码是什么?

7
我是一名有用的助手,可以为您翻译文本。
我有一个吵闹的Python脚本,想通过将其stderr输出重定向到/dev/null来使其保持安静(顺便说一下,使用的是bash)。
像这样:
python -u parse.py  1> /tmp/output3.txt 2> /dev/null

但它很快就会过早退出。嗯。我看不到回溯,因为当然它会随着stderr一起输出。如果我不把stderr指向某个地方,它会正常运行而不会有噪音。

所以让我们尝试将其重定向到某个文件而不是/dev/null,并查看它的输出:

python -u parse.py  1> /tmp/output3.txt 2> /tmp/foo || tail /tmp/foo

Traceback (most recent call last):
  File "parse.py", line 79, in <module>
    parseit('pages-articles.xml')
  File "parse.py", line 33, in parseit
    print >>sys.stderr, "bad page title", page_title
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

所以,生成的stderr包含utf8编码,但由于某种原因,当它被重定向时,Python拒绝打印非ASCII字符,即使它被重定向到/dev/null(当然,Python并不知道这一点)。

我该如何消除一个包含utf8编码的Python脚本的stderr输出?是否有任何方法可以在不重写此脚本中的每个stderr打印语句的情况下实现?

3个回答

5
您可以通过绑定到自定义编写器来静音stderr:
#!/usr/bin/env python
import codecs, sys

class NullWriter:
    def write(self, *args, **kwargs):
        pass

if len(sys.argv) == 2:
   if sys.argv[1] == '1':
      sys.stderr = NullWriter()
   elif sys.argv[1] == '2':
      #NOTE: sys.stderr.encoding is *read-only* 
      #      therefore the whole stderr should be replaced
      # encode all output using 'utf8'
      sys.stderr = codecs.getwriter('utf8')(sys.stderr)

print >>sys.stderr, u"\u20AC" # euro sign
print "ok"

例子:

$ python silence_stderr.py
Traceback (most recent call last):
  File "silence_stderr.py", line 11, in <module>
    print >>sys.stderr, u"\u20AC"
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)

标准错误被禁止输出:

$ python silence_stderr.py 1
ok

编码后的标准错误输出:

$ python silence_stderr.py 2
€
ok

注意: 上述输出是在emacs中获得的,因此要在终端中模拟它,您可以执行以下操作:

$ python ... 2>out.txt
$ cat out.txt

注意: 在 Windows 控制台中(在使用 chcp 65001 切换到 'utf-8' 并使用 TrueType 字体 (Lucida Console) 后),我得到了奇怪的结果:

C:\> python silence_stderr.py 2
Traceback (most recent call last):
  File "silence_stderr.py", line 14, in <module>
    print >>sys.stderr, u"\u20AC" # euro sign
  File "C:\pythonxy\python\lib\codecs.py", line 304, in write
    self.stream.write(data)
IOError: [Errno 13] Permission denied

如果字体不是truetype,则不会引发异常,但输出结果会出错。

Perl适用于truetype字体:

C:\> perl  -E"say qq(\x{20ac})"
Wide character in print at -e line 1.
€

重定向功能可用:

C:\>python silence_stderr.py 2 2>tmp.log
ok
C:\>cat tmp.log
€
cat: write error: Permission denied

重新评论

来自codecs.getwriter文档:

查找给定编码的编解码器,并返回其StreamWriter类或工厂函数。 如果找不到编码,则会引发LookupError

一个过度简化的观点:

class UTF8StreamWriter:
    def __init__(self, writer):
        self.writer = writer
    def write(self, s):
        self.writer.write(s.encode('utf-8'))

sys.stderr = UTF8StreamWriter(sys.stderr)

很酷啊...你能解释一下codecs.getwriter的作用吗? - ʞɔıu

4

当 stderr 没有重定向时,它会采用终端的编码方式。但是当你进行重定向时,这一切都变得不同了。你需要使用 sys.stderr.isatty() 来检测是否已经进行了重定向,并适当地进行编码。


实际上,sys.stderr.encoding并不是由终端的编码定义的,而终端的编码可能会因为各种未知于Python的原因而发生变化。更有可能的是,stderr.encoding是由LC_*环境变量或类似变量定义的。 - jfs
终端的编码是由这些变量确定的,因此最终结果是相同的。 - Ignacio Vazquez-Abrams
@Ignacio Vazquez-Abrams:这个问题非常复杂,例如,http://bugs.python.org/issue4947 - jfs
LC_* 是一个环境变量。它可能会被 xterm 等设置,但它并不决定 xterm 等使用的编码方式。 - mcr
LC_* 是由 shell 设置的,而不是由 xterm 设置的。同时,DO 命令可以确定编码(例如 xx_XX.UTF-8)。 - jellyfish

2

您也可以将字符串编码为ASCII,用不映射的unicode字符替换。这样您就不必担心终端类型了。

asciiTitle = page_title.encode("ascii", "backslashreplace")
print >>sys.stderr, "bad page title", asciiTitle

这将用反斜杠转义替换无法编码的字符,例如\xfc。 还有其他一些替换选项,在此处描述:

http://docs.python.org/library/stdtypes.html#str.encode


如果没有办法强制文件句柄成为Unicode,那么(backslashreplace)应该是stderr的默认值! - mcr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接