python调用pipe_python中的subprocess.PIPE上的非阻塞读取

fcntl,select,asyncproc在这种情况下无济于事。

无论操作系统如何,无阻塞地读取流的可靠方法是使用Queue.get_nowait():

import sys

from subprocess import PIPE, Popen

from threading import Thread

try:

from queue import Queue, Empty

except ImportError:

from Queue import Queue, Empty # python 2.x

ON_POSIX = 'posix' in sys.builtin_module_names

def enqueue_output(out, queue):

for line in iter(out.readline, b''):

queue.put(line)

out.close()

p = Popen(['myprogram.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX)

q = Queue()

t = Thread(target=enqueue_output, args=(p.stdout, q))

t.daemon = True # thread dies with the program

t.start()

# ... do other things here

# read line without blocking

try: line = q.get_nowait() # or q.get(timeout=.1)

except Empty:

print('no output yet')

else: # got line

# ... do something with line

我经常遇到类似的问题;我经常编写的Python程序需要能够执行一些主要功能,同时从命令行(stdin)接受用户输入。简单地将用户输入处理功能放在另一个线程中并不能解决问题,因为readline()阻塞并且没有超时。如果主要功能已经完成并且不再需要等待进一步的用户输入,我通常希望我的程序退出,但它不能,因为readline()仍然在等待一行的另一个线程中阻塞。我发现这个问题的解决方案是使用fcntl模块使stdin成为非阻塞文件:

import fcntl

import os

import sys

# make stdin a non-blocking file

fd = sys.stdin.fileno()

fl = fcntl.fcntl(fd, fcntl.F_GETFL)

fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

# user input handling thread

while mainThreadIsRunning:

try: input = sys.stdin.readline()

except: continue

handleInput(input)

在我看来,这比使用选择或信号模块解决这个问题要清晰一点,但是它再一次只适用于UNIX ...

Python 3.4为异步IO引入了新的临时API - asyncio模块。

这种方法类似于@Bryan Ward的基于twisted的答案 - 定义一个协议,一旦数据准备就调用它的方法:

#!/usr/bin/env python3

import asyncio

import os

class SubprocessProtocol(asyncio.SubprocessProtocol):

def pipe_data_received(self, fd, data):

if fd == 1: # got stdout data (bytes)

print(data)

def connection_lost(self, exc):

loop.stop() # end loop.run_forever()

if os.name == 'nt':

loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows

asyncio.set_event_loop(loop)

else:

loop = asyncio.get_event_loop()

try:

loop.run_until_complete(loop.subprocess_exec(SubprocessProtocol,

"myprogram.exe", "arg1", "arg2"))

loop.run_forever()

finally:

loop.close()

请参阅文档中的“子流程”。

有一个高级接口asyncio.create_subprocess_exec()返回Process对象,允许使用StreamReader.readline()协同程序异步读取一行

(使用async/awaitPython3.5+语法):

#!/usr/bin/env python3.5

import asyncio

import locale

import sys

from asyncio.subprocess import PIPE

from contextlib import closing

async def readline_and_kill(*args):

# start child process

process = await asyncio.create_subprocess_exec(*args, stdout=PIPE)

# read line (sequence of bytes ending with b'\n') asynchronously

async for line in process.stdout:

print("got line:", line.decode(locale.getpreferredencoding(False)))

break

process.kill()

return await process.wait() # wait for the child process to exit

if sys.platform == "win32":

loop = asyncio.ProactorEventLoop()

asyncio.set_event_loop(loop)

else:

loop = asyncio.get_event_loop()

with closing(loop):

sys.exit(loop.run_until_complete(readline_and_kill(

"myprogram.exe", "arg1", "arg2")))

readline_and_kill()执行以下任务:

启动子进程,将其stdout重定向到管道

异步读取子进程'stdout中的一行

杀死子进程

等它退出

如有必要,每个步骤都可以通过超时秒限制。

尝试使用asyncproc模块。例如:

import os

from asyncproc import Process

myProc = Process("myprogram.app")

while True:

# check to see if process has ended

poll = myProc.wait(os.WNOHANG)

if poll != None:

break

# print any new output

out = myProc.read()

if out != "":

print out

该模块负责S.Lott建议的所有线程。

您可以在Twisted中轻松完成此操作。根据您现有的代码库,这可能不是那么容易使用,但如果您正在构建一个扭曲的应用程序,那么这样的事情几乎变得微不足道。您创建一个ProcessProtocol类,并覆盖outReceived()方法。 Twisted(取决于所使用的反应器)通常只是一个很大的select()循环,其中安装了回调来处理来自不同文件描述符(通常是网络套接字)的数据。所以outReceived()方法只是安装一个回调来处理来自STDOUT的数据。演示此行为的简单示例如下:

from twisted.internet import protocol, reactor

class MyProcessProtocol(protocol.ProcessProtocol):

def outReceived(self, data):

print data

proc = MyProcessProtocol()

reactor.spawnProcess(proc, './myprogram', ['./myprogram', 'arg1', 'arg2', 'arg3'])

reactor.run()

Twisted文档有一些很好的信息。

如果你围绕Twisted构建整个应用程序,它会与本地或远程的其他进程进行异步通信,就像这样非常优雅。另一方面,如果你的程序不是建立在Twisted之上,那么这实际上并没有那么有用。希望这对其他读者有帮助,即使它不适用于您的特定应用程序。

使用选择&读(1)。

import subprocess #no new requirements

def readAllSoFar(proc, retVal=''):

while (select.select([proc.stdout],[],[],0)[0]!=[]):

retVal+=proc.stdout.read(1)

return retVal

p = subprocess.Popen(['/bin/ls'], stdout=subprocess.PIPE)

while not p.poll():

print (readAllSoFar(p))

对于readline() - 如:

lines = ['']

while not p.poll():

lines = readAllSoFar(p, lines[-1]).split('\n')

for a in range(len(lines)-1):

print a

lines = readAllSoFar(p, lines[-1]).split('\n')

for a in range(len(lines)-1):

print a

一种解决方案是使另一个进程执行您对进程的读取,或者使进程的线程超时。

这是超时函数的线程版本:

http://code.activestate.com/recipes/473878/

但是,你需要阅读stdout,因为它正在进入?

另一种解决方案可能是将输出转储到文件并等待进程使用p.wait()完成。

f = open('

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值