让我们考虑这个片段:
from subprocess import Popen, PIPE, CalledProcessError def execute(cmd): with Popen(cmd, shell=True, stdout=PIPE, bufsize=1, universal_newlines=True) as p: for line in p.stdout: print(line, end='') if p.returncode != 0: raise CalledProcessError(p.returncode, p.args) base_cmd = [ "cmd", "/c", "d:\\virtual_envs\\py362_32\\Scripts\\activate", "&&" ] cmd1 = " ".join(base_cmd + ['python -c "import sys; print(sys.version)"']) cmd2 = " ".join(base_cmd + ["python -m http.server"])
如果我运行execute(cmd1)
输出将被打印没有任何问题。
但是,如果我运行execute(cmd2)
而不是打印什么,为什么是这样的,我怎样才能解决它,所以我可以实时看到http.server的输出。
另外,如何for line in p.stdout
进行评估? 直到达到标准输出或某种东西,它是一种无尽的循环?
这个话题已经在这里讨论了几次,但我还没有find一个Windows解决scheme。 上面的代码实际上是代码从这个答案,并试图从virtualenv(Win7上的python3.6.2-32bits)运行http.server
如果你想从一个正在运行的子进程中连续读取,你必须使这个进程的输出无缓冲。 你的子进程是一个Python程序,可以通过将-u
传递给解释器来完成:
python -u -m http.server
这是它在Windows机器上的外观。
使用你的代码,你不能看到实时输出,因为缓冲^
for line in p.stdout: print(line, end='')
但是,如果你使用p.stdout.readline()
它应该工作:
while True: line = p.stdout.readline() if not line: break print(line, end='')
有关详细信息,请参阅相应的python bug讨论
UPD:在这里你可以找到几乎相同的问题与各种解决方案在stackoverflow。
如何对p.stdout中的行进行内部评估? 直到达到标准输出或某种东西,它是一种无尽的循环?
p.stdout
是一个缓冲区(阻塞)。 当你从一个空的缓冲区读取时,你将被阻塞,直到有东西写入到这个缓冲区。 一旦有东西在里面,你得到的数据和执行内部的一部分。
想想linux下的tail -f
是如何工作的:等到写入文件的东西被写入之后,再把新的数据回显到屏幕上。 当没有数据时会发生什么? 它等待。 所以当你的程序到达这条线时,它会等待数据并处理它。
当你的代码工作,但作为一个模型运行而不是,它必须以某种方式与此有关。 http.server
模块可能会缓冲输出。 尝试添加-u
参数到Python运行该进程作为无缓冲:
-u:无缓冲的二进制stdout和stderr; 还PYTHONUNBUFFERED = x请参阅手册页的内部缓冲有关'-u'
此外,你可能想尝试改变你的循环到for line in iter(lambda: p.stdout.read(1), ''):
因为这在处理之前每次读取1
个字节。
更新 :完整的循环代码是
for line in iter(lambda: p.stdout.read(1), ''): sys.stdout.write(line) sys.stdout.flush()
另外,你把你的命令作为一个字符串传递。 试着把它作为一个列表传递,每个元素都在自己的插槽中:
cmd = ['python', '-m', 'http.server', ..]
我认为主要的问题是http.server
以某种方式记录输出到stderr
,在这里我有一个asyncio
的例子,从stdout
或stderr
读取数据。
我的第一个尝试是使用asyncio,一个很好的API,它自Python 3.4以来就存在。 后来我发现了一个更简单的解决方案,所以你可以选择,他们都应该工作。
在后台asyncio使用IOCP – 一个Windows API异步的东西。
# inspired by https://pymotw.com/3/asyncio/subprocesses.html import asyncio import sys import time if sys.platform == 'win32': loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) async def run_webserver(): buffer = bytearray() # start the webserver without buffering (-u) and stderr and stdin as the arguments print('launching process') proc = await asyncio.create_subprocess_exec( sys.executable, '-u', '-mhttp.server', stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) print('process started {}'.format(proc.pid)) while 1: # wait either for stderr or stdout and loop over the results for line in asyncio.as_completed([proc.stderr.readline(), proc.stdout.readline()]): print('read {!r}'.format(await line)) event_loop = asyncio.get_event_loop() try: event_loop.run_until_complete(run_df()) finally: event_loop.close()
基于你的例子,这是一个非常简单的解决方案。 它只是将stderr重定向到stdout,只读stdout。
from subprocess import Popen, PIPE, CalledProcessError, run, STDOUT import os def execute(cmd): with Popen(cmd, stdout=PIPE, stderr=STDOUT, bufsize=1) as p: while 1: print('waiting for a line') print(p.stdout.readline()) cmd2 = ["python", "-u", "-m", "http.server"] execute(cmd2)
您可以在操作系统级别实施无缓冲区行为。
在Linux中,你可以用stdbuf
来包装现有的命令行:
stdbuf -i0 -o0 -e0 YOURCOMMAND
或者在Windows中,你可以用winpty
来包装你现有的命令行:
winpty.exe -Xallow-non-tty -Xplain YOURCOMMAND
我不知道这种操作系统中立的工具。