我能想到几个解决方案。
#1:您可以直接进入源代码获取the code for communicate,复制并粘贴它,添加代码以打印每一行,并进行缓冲。 (如果你自己的stdout 可能因为一个死锁的父级而阻塞,你可以使用threading.Queue 或其他东西。)这显然有点hacky,但它很容易,而且很安全。
但实际上,communicate 很复杂,因为它需要完全通用,并处理您不需要的情况。您需要的只是核心技巧:在问题上抛出线程。您只需要一个不会在read 调用之间造成任何缓慢或阻塞的专用读取器线程。
类似这样的:
self.process = subprocess.Popen(self.cmd, stdout=subprocess.PIPE)
lines = []
def reader():
for line in self.process.stdout:
lines.append(line)
sys.stdout.write(line)
t = threading.Thread(target=reader)
t.start()
self.process.wait()
t.join()
您可能需要在reader 线程中进行一些错误处理。而且我不是 100% 确定您可以在这里安全地使用readline。但这要么有效,要么接近。
#2: 或者你可以创建一个包装类,它接收一个文件对象并在每次有人从它reads 时发送到stdout/stderr。然后手动创建管道,并传入包裹的管道,而不是使用自动魔术PIPE。这与 #1 有完全相同的问题(意味着没有问题,或者您需要使用 Queue 或 sys.stdout.write 可以阻止的东西)。
类似这样的:
class TeeReader(object):
def __init__(self, input_file, tee_file):
self.input_file = input_file
self.tee_file = tee_file
def read(self, size=-1):
ret = self.input_file.read(size)
if ret:
self.tee_file.write(ret)
return ret
换句话说,它包装了一个文件对象(或类似文件的东西),并像一个文件对象一样工作。 (当您使用PIPE 时,process.stdout 在 Unix 上是一个真实的文件对象,但可能只是在 Windows 上的行为。)您需要委托给 input_file 的任何其他方法都可以直接委托,无需任何额外的包装。试试这个,看看communicate 得到什么方法AttributeExceptions 寻找并明确编码这些方法,或者使用通常的__getattr__ 技巧来委派所有内容。 PS,如果你担心这个“文件对象”的概念意味着磁盘存储,请阅读维基百科上的Everything is a file。
#3:最后,您可以获取 PyPI 上的“异步子进程”模块之一,或者包含在 twisted 或其他异步框架中并使用它。 (这使得 可能 避免死锁问题,但这不是保证 - 您仍然必须确保正确地服务管道。)