将sys.stdout缓冲区设置为零的Python标准习惯用法不适用于Unicode

Mar*_*ann 6 python unicode buffer stdout

当我在Python中编写sysadmin脚本时,sys.stdout上的缓冲区会影响对print()的每次调用,这很烦人,因为我不想等待刷新缓冲区然后获取大量的行一旦在屏幕上,我想在脚本生成新输出后立即获得单独的输出行.我甚至不想等待换行,所以看看输出.

在python中经常使用的成语是

import os
import sys
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)
Run Code Online (Sandbox Code Playgroud)

这对我来说很好.现在我注意到它不适用于Unicode.请参阅以下脚本:

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

from __future__ import print_function, unicode_literals

import os
import sys

print('Original encoding: {}'.format(sys.stdout.encoding))
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)
print('New encoding: {}'.format(sys.stdout.encoding))

text = b'Eisb\xe4r'
print(type(text))
print(text)

text = text.decode('latin-1')
print(type(text))
print(text)
Run Code Online (Sandbox Code Playgroud)

这导致以下输出:

Original encoding: UTF-8
New encoding: None
<type 'str'>
Eisb?r
<type 'unicode'>
Traceback (most recent call last):
  File "./export_debug.py", line 18, in <module>
    print(text)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 4: ordinal not in range(128)
Run Code Online (Sandbox Code Playgroud)

我花了几个小时来追查它的原因(我的原始脚本比这个最小的调试脚本长得多).这是线

sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)
Run Code Online (Sandbox Code Playgroud)

我用了多年,所以没想到它有任何问题.只需注释掉这一行,正确的输出应如下所示:

Original encoding: UTF-8
New encoding: UTF-8
<type 'str'>
Eisb?r
<type 'unicode'>
Eisbär
Run Code Online (Sandbox Code Playgroud)

那么剧本是做什么的?为了使我的Python 2.7代码尽可能接近Python 3.x,我总是在使用

from __future__ import print_function, unicode_literals
Run Code Online (Sandbox Code Playgroud)

这使得python使用新的print() - 函数但更重要的是:它使Python默认将所有字符串存储为Unicode内部.例如,我有很多Latin-1/ISO-8859-1编码数据

text = b'Eisb\xe4r'
Run Code Online (Sandbox Code Playgroud)

要以预期的方式使用它,我需要首先将其解码为Unicode,这就是什么

text = text.decode('latin-1')
Run Code Online (Sandbox Code Playgroud)

是为了.由于我的系统上的默认编码是UTF-8,每当我打印一个字符串时,python就会将内部Unicode字符串编码为UTF-8.但首先它必须在内部完美的Unicode.

现在一切正常,但到目前为止还没有使用零字节输出缓冲区.有任何想法吗?我注意到在零缓冲行之后未设置sys.stdout.encoding,但我不知道如何再次设置它.它是一个只读属性,OS环境变量LC_ALL或LC_CTYPE似乎只在python解释器的开头进行计算.

顺便说一下:'Eisbär'是德语中的'北极熊'.

Mar*_*ers 6

print函数在写入文件对象时使用特殊标志,导致PyFile_WriteObjectPython C API 的函数检索输出编码以执行unicode-to-bytes转换,并通过替换stdout丢失编码的流.不幸的是,您无法再次明确设置它:

encoding = sys.stdout.encoding
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)
sys.stdout.encoding = encoding  # Raises a TypeError; readonly attribute
Run Code Online (Sandbox Code Playgroud)

您也不能使用该io.open功能,因为如果您希望能够使用encoding您需要的选项,则不允许禁用缓冲.

立即刷新打印功能的正确方法是使用flush=True关键字:

print(something, flush=True)
Run Code Online (Sandbox Code Playgroud)

如果在任何地方添加太繁琐,请考虑使用自定义打印功能:

def print(*args, **kw):
    flush = kw.pop('flush', True)  # Python 2.7 doesn't support the flush keyword..   
    __builtins__.print(*args, **kw)
    if flush:
        sys.stdout.flush()
Run Code Online (Sandbox Code Playgroud)

由于Python 2.7的print()功能实际上并不支持 flush关键字(botheration),因此您可以通过在该自定义版本中添加显式刷新来模拟它.