python調用外部子進程,經過管道實現異步標準輸入和輸出的交互

咱們一般會遇到這樣的需求:經過C++或其餘較底層的語言實現了一個複雜的功能模塊,須要搭建一個基於Web的Demo,方法查詢數據。因爲Python語言的強大和簡潔,其用來搭建Demo很是合適,Flask框架和jinja2模塊功能爲python提供了方便的web開發能力。同時,python可以很方便的同其餘語言的代碼交互。所以咱們選擇python做爲開發Demo的工具。假設咱們須要調用的模塊(提供底層服務)經過標準輸入循環讀入數據,處理完畢後把結果寫出到標出輸出,這樣的場景在Linux環境下很常見,依賴於Linux強大的重定向能力。然而,很是不幸的是,底層模塊有一個很重的初始化過程,所以咱們不可以每次查詢請求都去從新生成調用底層模塊的子進程。解決方案就是隻生成一次子進程,而後對每一個請求經過管道(pipe)來和子進程交互。python

Python的subprocess模塊能夠很容易地生成子進程,相似Linux系統調用fork和exec。subprocess模塊的Popen對象可能以非阻塞的方式調用外部可執行程序,所以咱們使用Poen對象來實現需求。若是咱們想要把數據寫入子進程的標準輸入stdin,那麼在建立Popen對象的時候就須要指定參數stdin爲subprocess.PIPE;一樣,若是咱們須要從子進程的標準輸出中讀取數據,那麼在建立Popen對象的時候就須要指定參數stdout爲subprocess.PIPE。先看一個簡單的例子:web

from subprocess import Popen, PIPE

p = Popen('less', stdin=PIPE, stdout=PIPE)
p.communicate('Line number %d.\n' % x)

communicate函數返回一個二元組(stdoutdata, stderrdata),包含了子進程的標準輸出和標出錯誤的輸出數據。然而,因爲Popen對象的communicate函數會阻塞父進程,同時還會關閉管道,所以每一個Popen對象只能調用一次communicate函數,若是有多個請求必須從新生成Popen對象(從新初始化子進程),不能知足咱們的需求。框架

所以,咱們只有往Popen對象的stdin和stdout對象裏寫入和讀取數據才能實現咱們的需求。然而,不幸的是subprocess模塊默認狀況下只運行在子進程結束的時候讀取一次標準輸出。Both subprocess and os.popen* only allow input and output one time, and the output to be read only when the process terminatesless

進過一番研究以後我發現經過fcntl模塊的fcntl函數能夠把子進程的標準輸出改成非阻塞的方式,從而達到咱們的目的。這樣困擾我許久的問題終於獲得了完美解決。代碼以下:函數

 1 #!/usr/bin/python                                                                                                                                                      
 2 # -*- coding: utf-8 -*-
 3 # author: 
 4 from subprocess import Popen, PIPE
 5 import select
 6 import fcntl, os
 7 import time
 8 
 9 class Server(object):
10   def __init__(self, args, server_env = None):
11     if server_env:
12       self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=server_env)
13     else:
14       self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
15     flags = fcntl.fcntl(self.process.stdout, fcntl.F_GETFL)
16     fcntl.fcntl(self.process.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
17 
18   def send(self, data, tail = '\n'):
19     self.process.stdin.write(data + tail)
20     self.process.stdin.flush()
21 
22   def recv(self, t=.1, stderr=0):
23     r = ''
24     pr = self.process.stdout
25     if stderr:
26       pr = self.process.stdout
27     while True:
28       if not select.select([pr], [], [], 0)[0]:
29         time.sleep(t)
30         continue
31       r = pr.read()
32       return r.rstrip()
33     return r.rstrip()
34 
35 if __name__ == "__main__":
36   ServerArgs = ['/path/to/server', '/path/to/args']
37   server = Server(ServerArgs)
38   test_data = '拿鐵', '咖啡'
39   for x in test_data:
40     server.send(x)
41     print x, server.recv()     

 另外,調用一些外部程序時,可能須要指定相應的環境變量,方式以下:工具

  my_env = os.environ
  my_env["LD_LIBRARY_PATH"] = "/path/to/lib"
  server = server.Server(cmd, my_env)
相關文章
相關標籤/搜索