强制子流程.Popen使用write()函数而不是fileno()将stdout/stderr写入python中的类文件



我的目标是在python中使用subprocess.Popen打开一个进程,并让这个进程将其stdout和stderr管道连接到我编写的自定义RingBuffer类,使我能够定期从实例化子进程的同一空间检查缓冲区的内容。这很重要,我知道有一些方法可以制作一个单独的程序,将子进程的输出管道传输到该环形缓冲区程序的stdin,但随后我必须手动检查一些包含环形缓冲区内容的底层文件,等等。理想的做法是将子程序的输出连接到我可以访问的某个对象。

首先,从subprocess(python 2.X(的文档(https://docs.python.org/2/library/subprocess.html)

stdin、stdout和stderr指定执行程序的标准输入、标准输出和标准错误文件句柄。有效值为PIPE、现有文件描述符(正数integer(、现有文件对象和None。PIPE表示应该创建到子对象的管道。默认设置为无,不会发生重定向;子级的文件句柄将是从父代继承。此外,stderr可以是STDOUT指示来自子进程的stderr数据应为捕获到与stdout 相同的文件句柄中

"一个现有的文件对象",所以我假设如果我创建一个符合file接口的类,它应该可以工作,对吧?

假设我已经做了一个像这样的类

class RingBuffer(object):
def __init__(max_size=1024*1024):
self.max_size = max_size
self.current_size = 0

def write(self, data):
self.current_size += len(data)
self.data.append(data)
if self.current_size >= self.max_size_bytes:
while self.current_size >= self.trim_size_bytes:
try:
popped = self.data.pop()
self.current_size -= len(popped)
except IndexError as e:
break
def writelines(self, sequence):
for item in sequence:
self.write(item)
def dumps(self):
ret = [line for line in self.data]
return 'n'.join(ret)
def clear(self):
self.data.clear()
self.current_size = 0

考虑到这个程序中可能存在错误,但你明白了要点,它公开了一个write()函数,并将数据写入一个循环缓冲区,当它出现错误时,将缓冲区修剪到一定的大小,并让用户在需要时使用dumps()函数恢复数据。

现在,如果我尝试类似的东西

r = RingBuffer()
pr = subprocess.Popen(["timeout", "15", "yes"], stdout=r, stderr=subprocess.STDOUT)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 382, in __init__
errread, errwrite), to_close = self._get_handles(stdin, stdout, stderr)
File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 818, in _get_handles
c2pwrite = stdout.fileno()
AttributeError: 'RingBuffer' object has no attribute 'fileno'

好吧,所以我的"类文件"对象缺少fileno()函数以符合文件接口。这就是问题所在。。为什么它需要fileno?为什么它不能只使用我提供的write()功能?我假设它将绕过我的write函数,而是使用fileno直接写入文件?

假设我添加了一个函数的存根

def fileno()
return None

然后发生

r = RingBuffer()
pr = subprocess.Popen(["timeout", "15", "yes"], stdout=r, stderr=subprocess.STDOUT)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 390, in __init__
errread, errwrite)
File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1024, in _execute_child
raise child_exception
OSError: [Errno 2] No such file or directory

所以我的问题是:我如何才能强制subprocess.Popen对类似file的对象使用write()函数,而不是试图直接写入从不存在的fileno()函数返回的文件句柄?如果没有办法做到这一点。。有什么办法可以在这里实现我想要的吗?

我知道理论上我可以制作一些文件/tmp/ringlog.txt,并在类的实例化时打开该文件,然后让程序写入该文件,让我的程序定期查看该文件,并使用类似的环形缓冲区算法将其保存在max_size下,但这太混乱了。

另一种选择是制作一个程序,读取stdin,写入文件,并对内容进行环形缓冲,以将文件保持在一定的大小下,但我仍在处理实际的文件,我只想将内容保持在内存中,并可从调用python环境访问。

子进程将使用标准操作系统级别的文件写入调用写入其stdout,这意味着它需要与这些调用兼容的东西。子进程无法查看Python的内存或调用Python对象上的方法。

如果您想将子流程的输出写入一个类似文件的对象,而该对象并不代表操作系统可以将其视为文件的东西,那么您必须通过管道接收输出,并亲自将其写入类似文件的目标。您可以为此生成一个工作线程(如果您计划在工作线程终止之前从中读取,请确保同步对对象的访问(,但直接与管道交互可能更简单。

如果您要使用子流程。Popen,然后我建议理解使用管道所带来的问题,通常是死锁。

请参阅:https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/

最新更新