2012-06-08 35 views
2

我想創建一個流對象,在任何時候寫入數據時觸發一個回調函數。監視流類

class MonitoredStream(): 
    def __init__(self, outstream, callback): 
     self.outstream = outstream 
     self.callback = callback 

    def write(self, s): 
     self.callback(s) 
     self.outstream.write(s) 

    def __getattr__(self, attr): 
     return getattr(self.outstream, attr) 

這工作得很好,當我直接調用write方法,但我很想擁有它也工作時,我有卡掛於流子輸出。例如:

def f(s): 
    print("Write") 

p = sub.Popen(["sh", "test.sh"], stdout=MonitoredStream(sys.stdout, f)) 
p.communicate() 

這只是將輸出直接發送到sys.stdout,完全繞過寫入功能。有沒有辦法可以監控這個輸出呢?

回答

2

我相信這裏的問題是,subprocess.Popen不使用Python接口管道 - 它取而代之的是獲取文件描述符,然後用它直接寫入管道,當你給stdout的屬性管道,意味着它使用它,繞過你的代碼。

我最好的解決方法就是在中間放置一個新的管道,讓你自己處理流。我會實現這個作爲一個上下文管理器:

import sys 
import os 
from subprocess import Popen 
from contextlib import contextmanager 

@contextmanager 
def monitor(stream, callback): 
    read, write = os.pipe() 
    yield write 
    os.close(write) 
    with os.fdopen(read) as f: 
     for line in f: 
      callback(line) 
      stream.write(line) 

def f(s): 
    print("Write") 

with monitor(sys.stdout, f) as stream: 
    p = Popen(["ls"], stdout=stream) 
    p.communicate() 

雖然你可以,當然,仍然使用類:

import sys 
import os 
from subprocess import Popen 

class MonitoredStream(): 
    def __init__(self, stream, callback): 
     self.stream = stream 
     self.callback = callback 
     self._read, self._write = os.pipe() 

    def fileno(self): 
     return self._write 

    def process(self): 
     os.close(self._write) 
     with os.fdopen(self._read) as f: 
      for line in f: 
       self.callback(line) 
       self.stream.write(line) 

def f(s): 
    print("Write") 

stream = MonitoredStream(sys.stdout, f) 
p = Popen(["ls"], stdout=stream) 
p.communicate() 
print(stream.process()) 

雖然我覺得這是不太優雅。

+0

這些都是很好的想法,但如果我正確理解它們,它們都會等到流關閉之後再將回調函數應用於整個輸出。理想情況下,我希望在任何時候有一行可用於讀取流時應用回調函數。 –

+0

這是真的,雖然,據我所知,'subprocess.communicate()'已經這樣做了,所以它應該是一個非問題。 –

+0

哦,我明白了。如果是這種情況,我將不得不尋找另一種方法。我的用例是我有一個長時間運行的進程生成日誌輸出,並且我希望能夠監視輸出並以實時方式做出反應(在進程完成之前)。我只是將輸出設置爲'subprocess.PIPE'並逐行讀取而不是使用'subprocess.communicate()' –