【问题标题】:Python standard idiom to set sys.stdout buffer to zero doesn't work with Unicode将 sys.stdout 缓冲区设置为零的 Python 标准习语不适用于 Unicode
【发布时间】:2012-10-10 17:32:00
【问题描述】:

当我在 Python 中编写 sysadmin 脚本时,影响每次调用 print() 的 sys.stdout 上的缓冲区很烦人,因为我不想等待缓冲区被刷新然后得到一大块屏幕上的行一次,相反,我想在脚本生成新输出后立即获得单独的输出行。我什至不想等待换行,所以请查看输出。

在python中经常使用的一个习惯用法是

import os
import sys
sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)

这对我来说很好用了很长时间。现在我注意到,它不适用于 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)

这导致以下输出:

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)

我花了几个小时才找到它的原因(我的原始脚本比这个最小的调试脚本要长得多)。就是这条线

sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)

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

Original encoding: UTF-8
New encoding: UTF-8
<type 'str'>
Eisb▒r
<type 'unicode'>
Eisbär

那么脚本要做什么呢?为了让我的 Python 2.7 代码尽可能接近 Python 3.x,我一直在使用

from __future__ import print_function, unicode_literals

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

text = b'Eisb\xe4r'

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

text = text.decode('latin-1')

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

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

顺便说一句:“Eisbär”是德语中“北极熊”的意思。

【问题讨论】:

  • @martineau 好吧,sys.stdout = codecs.getwriter('utf8')(sys.stdout) 提案也不起作用。我真的尝试并搜索了很多。所以我想没有经过测试的想法没有多大帮助。
  • 我已为您迁移了问题。下一次,只需flag 版主关注并告诉我们您的需求! :)
  • @MartenLehmann:它未经测试的事实是我将其发布为评论而不是答案的原因。
  • 你考虑过:alias python="python -u"没有修改 sys.stdout。顺便说一句,codecs.getwriter... 仅当您(以及您使用的所有库)打印 Unicode 文本时才有效(因此一般不建议使用)。

标签: python unicode buffer stdout


【解决方案1】:

打印函数在写入文件对象时使用特殊标志,导致 Python C API 的 PyFile_WriteObject 函数检索输出编码以进行 unicode 到字节的转换,并通过替换 stdout流您丢失了编码。不幸的是,您无法再次显式设置它:

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

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

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

print(something, flush=True)

如果太繁琐而无法到处添加,请考虑使用 自定义 打印功能:

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

由于 Python 2.7 的 print() 函数实际上还不支持 flush 关键字(麻烦),您可以通过在该自定义版本中添加显式刷新来模拟它。

【讨论】:

  • 您可以将自定义print() 函数的前三行替换为:flush = kw.pop('flush', True)
  • @Tadeck:添加了很棒的建议。
【解决方案2】:

sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)'wb' 参数中的 b 表示文件应该在 二进制模式 中位于 opened,这就是 Unicode 不起作用的原因。此外,在 Python 3 中,我无法将普通字符串打印到以这种方式配置的标准输出;上面写着TypeError: a bytes-like object is required, not 'str'

对于提到的“系统管理员脚本”用例,使用行缓冲就足够了,即每当写入换行符时,输出就会被刷新,例如在每个普通@的末尾987654326@ 声明。对于行缓冲,写就足够了:

import os
import sys

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1)  # 1 : line buffered

我发现这是必要的,以便在标准输出被重定向到另一个程序可能读取的管道(最简单的情况:./myprogram.py | cat)时进行逐行输出。

如果您需要立即冲洗部分线路,您可以使用:

print("mytext", end="", flush=True)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-04
    • 2010-12-02
    • 2015-11-10
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多