【问题标题】:Python 3: Unbuffered vs Buffered StreamsPython 3:无缓冲与缓冲流
【发布时间】:2015-01-26 23:46:01
【问题描述】:

我一直在使用以下 sn-p 来使我的 Python 脚本中调用的 C 代码静音(重定向输出):

from ctypes import CDLL, c_void_p
import os
import sys

# Code
class silence(object):
    def __init__(self, stdout=os.devnull):
        self.outfile = stdout

    def __enter__(self):
        # Flush
        sys.__stdout__.flush()

        # Save
        self.saved_stream = sys.stdout
        self.fd = sys.stdout.fileno()
        self.saved_fd = os.dup(self.fd)

        # Open the redirect
        self.new_stream = open(self.outfile, 'wb', 0)

        self.new_fd = self.new_stream.fileno()

        # Replace
        os.dup2(self.new_fd, self.fd)

    def __exit__(self, *args):
        # Flush
        self.saved_stream.flush()

        # Restore
        os.dup2(self.saved_fd, self.fd)
        sys.stdout = self.saved_stream

        # Clean up
        self.new_stream.close()
        os.close(self.saved_fd)


# Test case
libc = CDLL('libc.so.6')


# Silence!
with silence():
    libc.printf(b'Hello from C in silence\n')

这个想法是重定向与stdout 关联的fd,并将其替换为与打开的空设备关联的fd。不幸的是,它在 Python 3 下无法按预期工作:

$ python2.7 test.py
$ python3.3 -u test.py
$ python3.3 test.py
Hello from C in silence

在具有无缓冲输出的 Python 2.7 和 3.3 下,它确实工作。但是,我不确定根本原因是什么。即使stdout 被缓冲,对sys.saved_stream.flush() 的调用也应该最终在C 级别调用fflush(stdout)(将输出刷新到空设备)。

我误解了 Python 3 I/O 模型的哪一部分?

【问题讨论】:

  • 如果对标准输出的写入来自 C 库代码(例如 printf 和朋友),redirect_stdout 没有帮助。
  • 阅读完整答案(注意函数名)
  • 抱歉错字,我的评论是关于答案中提供的 stdout_redirected 函数。当 C 库 stdio 函数与他们自己的缓冲品牌混合时,它不会处理这种情况(因此会遇到与上述 sn-p 相同的问题)。
  • 是的,这可能是因为 Python 3 I/O(与 Python 2 不同)不使用 stdio 处理标准流,因此调用 stdout.flush() 可能还不够。如果您按照@Kolmar 的建议添加相应的libc.fflush(None) 调用,是否有帮助?如果您只对 stdio 流感兴趣,那么您可以使用 freopen() 函数来重定向标准 stdio 流。

标签: python python-3.x


【解决方案1】:

我也不是 100% 确定我了解 Py3 I/O 模型,但是添加

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

在您分配给self.fd 之后,在 Python 3.4 中为我修复了它(在添加此语句之前,我能够在 3.4 中重现该问题)。

【讨论】:

    【解决方案2】:

    我也不完全确定发生了什么,但在我的系统上,有两种方法可以解决这个问题:

    • __exit__ 中对self.saved_stream.flush() 的调用替换为libc.fflush(None)
    • 在调用silence()之前使用任意字符串调用libc.printf,例如:

      libc = CDLL('/bin/cygwin1.dll')
      libc.printf(b'')
      

    另外,只有第二种方式,Python 的 printlibc.printf 的输出在 with silence(): 块之后保持同步。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-06
      • 2010-11-29
      • 1970-01-01
      • 2020-09-03
      • 2020-11-22
      • 2012-10-08
      • 1970-01-01
      相关资源
      最近更新 更多