关于unicode:管道输出导致python程序失败

Piping output causes python program to fail

本问题已经有最佳答案,请猛点这里访问。

我有以下简单程序:

1
2
3
4
5
# -*- coding: utf-8 -*-

GREEK = u'ΑΒΓΔ ΕΖΗΘ ΙΚΛΜ ΝΞΟΠ ΡΣΤΥ ΦΧΨΩ αβγδ εζηθ ικλμ νξοπ ρ?τυ φχψω'

print GREEK

在终端上运行此命令会产生预期的结果:

1
2
$ python test.py
ΑΒΓΔ ΕΖΗΘ ΙΚΛΜ ΝΞΟΠ ΡΣΤΥ ΦΧΨΩ αβγδ εζηθ ικλμ νξοπ ρ?τυ φχψω

但是通过管道将输出传输到另一个程序会导致错误:

1
2
3
4
5
6
7
8
9
10
$ python test.py | less

Traceback (most recent call last):
  File"test.py", line 5, in <module>
    print GREEK
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)
Traceback (most recent call last):
  File"ddd.py", line 5, in <module>
    print GREEK
UnicodeEncodeError: '
ascii' codec can't encode characters in position 0-3: ordinal not in range(128)
  • 为什么失败了?为什么重定向会影响程序的运行方式?我本以为在shell中运行的程序总是被重定向的:有时重定向到终端程序,有时重定向到另一个程序(在本例中是less)。为什么"目的地"程序会影响源程序的执行?
  • 我能做些什么来确保程序独立运行,不管它是发送到终端还是另一个目的地?


基于这一点以及其他相关问题,我已经实现了以下解决方案,它看起来非常健壮,不需要对代码库的所有打印语句进行任何更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding: utf-8 -*-

import sys

def set_output_encoding(encoding='utf-8'):
    import sys
    import codecs
    '''When piping to the terminal, python knows the encoding needed, and
       sets it automatically. But when piping to another program (for example,
       | less), python can not check the output encoding. In that case, it
       is None. What I am doing here is to catch this situation for both
       stdout and stderr and force the encoding'''

    current = sys.stdout.encoding
    if current is None :
        sys.stdout = codecs.getwriter(encoding)(sys.stdout)
    current = sys.stderr.encoding
    if current is None :
        sys.stderr = codecs.getwriter(encoding)(sys.stderr)

GREEK = u'ΑΒΓΔ ΕΖΗΘ ΙΚΛΜ ΝΞΟΠ ΡΣΤΥ ΦΧΨΩ αβγδ εζηθ ικλμ νξοπ ρ?τυ φχψω'

set_output_encoding()

print GREEK
print >> sys.stderr, GREEK

测试:

1
2
3
python ddd.py             # Do not pipe anything
python ddd.py | less      # Pipe stdout, let stderr go to the terminal
python ddd.py 2>&1 | less # Pipe both stdout and stderr to less

所有这些都产生了预期的:

1
2
ΑΒΓΔ ΕΖΗΘ ΙΚΛΜ ΝΞΟΠ ΡΣΤΥ ΦΧΨΩ αβγδ εζηθ ικλμ νξοπ ρ?τυ φχψω
ΑΒΓΔ ΕΖΗΘ ΙΚΛΜ ΝΞΟΠ ΡΣΤΥ ΦΧΨΩ αβγδ εζηθ ικλμ νξοπ ρ?τυ φχψω


输出程序的编码不支持字符。另一种方法是始终对程序中的任何内容进行编码,并在需要时将其解码回来。

1
2
3
4
5
# -*- coding: utf-8 -*-

GREEK = u'ΑΒΓΔ ΕΖΗΘ ΙΚΛΜ ΝΞΟΠ ΡΣΤΥ ΦΧΨΩ αβγδ εζηθ ικλμ νξοπ ρ?τυ φχψω'

print GREEK.encode('utf-8')

不过,这是可行的,因为您的终端应用程序不使用相同的编码,所以它只显示已编码的字符串,而不是原始字符串。