簡體   English   中英

子進程,從 STDOUT 讀取時重復寫入 STDIN (Windows)

[英]Subprocess, repeatedly write to STDIN while reading from STDOUT (Windows)

我想從 python 調用一個外部進程。我調用的進程讀取一個輸入字符串並給出標記化結果,然后等待另一個輸入(如果有幫助,二進制是 MeCab 標記器)。

我需要通過調用此過程來標記數千行字符串。

問題是Popen.communicate()工作但在給出 STDOUT 結果之前等待進程結束。 我不想繼續關閉和打開新的子流程數千次。 (而且我不想發送整個文本,將來它很容易超過數萬行。)

from subprocess import PIPE, Popen

with Popen("mecab -O wakati".split(), stdin=PIPE,
           stdout=PIPE, stderr=PIPE, close_fds=False,
           universal_newlines=True, bufsize=1) as proc:
    output, errors = proc.communicate("foobarbaz")

print(output)

我試過讀取proc.stdout.read()而不是使用 communicate 但它被stdin阻止並且在proc.stdin.close()之前不返回任何結果。 這又意味着我每次都需要創建一個新流程。

我試圖從一個類似的問題中實現隊列和線程,如下所示,但它要么不返回任何東西,因此它停留在While True上,或者當我通過重復發送字符串強制 stdin 緩沖區填充時,它輸出所有結果一次。

from subprocess import PIPE, Popen
from threading import Thread
from queue import Queue, Empty

def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()

p = Popen('mecab -O wakati'.split(), stdout=PIPE, stdin=PIPE,
          universal_newlines=True, bufsize=1, close_fds=False)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True
t.start()

p.stdin.write("foobarbaz")
while True:
    try:
        line = q.get_nowait()
    except Empty:
        pass
    else:
        print(line)
        break

還查看了 Pexpect 路由,但它的 windows 端口不支持一些重要的模塊(基於 pty 的模塊),所以我也無法應用它。

我知道有很多類似的答案,而且我已經嘗試了其中的大部分。 但我嘗試過的任何東西似乎都不適用於 Windows。

編輯:關於我正在使用的二進制文件的一些信息,當我通過命令行使用它時。 它運行並標記我給出的句子,直到我完成並強行關閉程序。

(...waits_for_input -> input_recieved -> output -> waits_for_input...)

謝謝。

如果 mecab 使用帶有默認緩沖的 C FILE流,則管道標准輸出具有 4 KiB 緩沖區。 這里的想法是程序可以有效地使用小的、任意大小的讀取和寫入緩沖區,並且底層標准 I/O 實現處理自動填充和刷新更大的緩沖區。 這最大限度地減少了所需的系統調用數量並最大限度地提高了吞吐量。 顯然,您不希望這種行為用於交互式控制台或終端 I/O 或寫入stderr 在這些情況下,C 運行時使用行緩沖或不使用緩沖。

程序可以覆蓋此行為,有些程序確實具有用於設置緩沖區大小的命令行選項。 例如,Python 具有“-u”(無緩沖)選項和PYTHONUNBUFFERED環境變量。 如果 mecab 沒有類似的選項,則 Windows 上沒有通用的解決方法。 C 運行時情況太復雜了。 Windows 進程可以靜態或動態鏈接到一個或多個 CRT。 Linux 上的情況不同,因為 Linux 進程通常將單個系統 CRT(例如 GNU libc.so.6)加載到全局符號表中,這允許LD_PRELOAD庫配置 C FILE流。 Linux stdbuf使用這個技巧,例如stdbuf -o0 mecab -O wakati


試驗的一種選擇是調用CreateConsoleScreenBuffer並從msvcrt.open_osfhandle獲取句柄的msvcrt.open_osfhandle 然后將其作為stdout而不是使用管道傳遞。 子進程會將其視為 TTY 並使用行緩沖而不是完整緩沖。 然而,管理這一點並非易事。 這將涉及讀取(即ReadConsoleOutputCharacter )一個由另一個進程主動寫入的滑動緩沖區(調用GetConsoleScreenBufferInfo來跟蹤光標位置)。 這種互動不是我曾經需要甚至嘗試過的。 但是我以非交互方式使用了控制台屏幕緩沖區,即在孩子退出后讀取緩沖區。 這允許從直接寫入控制台而不是stdout程序讀取多達 9,999 行的輸出,例如調用WriteConsole或打開“CON”或“CONOUT$”的程序。

這是 Windows 的解決方法。 這也應該適用於其他操作系統。 下載像 ConEmu ( https://conemu.github.io/ ) 這樣的控制台模擬器,啟動它而不是 mecab 作為你的子進程。

p = Popen(['conemu'] , stdout=PIPE, stdin=PIPE,
      universal_newlines=True, bufsize=1, close_fds=False)

然后將以下內容作為第一個輸入發送:

mecab -O wakafi & exit

您讓模擬器為您處理文件輸出問題; 當您手動與它交互時,它通常會這樣做。 我還在研究這個; 但看起來已經很有希望了......

唯一的問題是 conmu 是一個 gui 應用程序; 因此,如果沒有其他方法可以連接到其輸入和輸出,則可能必須從源代碼(它是開源的)進行調整和重建。 我還沒有找到任何其他方式; 但這應該有效。

我曾問到某種控制台模式下運行的問題在這里; 所以你也可以檢查那個線程的東西。 作者馬克西姆斯在 SO...

編碼

while True:
    try:
        line = q.get_nowait()
    except Empty:
        pass
    else:
        print(line)
        break

本質上是一樣的

print(q.get())

除了效率較低,因為它在等待時會消耗 CPU 時間。 顯式循環不會使來自子進程的數據更快到達; 它到了就到了。

對於處理不合作的二進制文件,我有一些建議,從最好到最壞:

  1. 找到一個 Python 庫並使用它。 MeCab 源代碼樹中似乎有一個官方的 Python 綁定,我在 PyPI 上看到了一些預構建的包。 您還可以查找可以使用ctypes或其他 Python FFI 調用的 DLL 構建。 如果那不起作用...

  2. 找到在每行輸出后刷新的二進制文件。 我在網上找到的最新 Win32 版本 v0.98 會在每行之后刷新。 失敗了...

  3. 構建您自己的二進制文件,在每行之后刷新。 找到主循環並在其中插入刷新調用應該很容易。 但是MeCab 似乎已經明確刷新了,並且 git blame 說刷新語句最后一次更改是在 2011 年,所以我很驚訝您曾經遇到過這個問題,並且我懷疑您的 Python 代碼中可能只是一個錯誤。 失敗了...

  4. 異步處理輸出。 如果您擔心出於性能原因想要與標記化並行處理輸出,您通常可以在第一個 4K 之后這樣做。 只需在第二個線程中進行處理,而不是將行塞入隊列。 如果你做不到那...

  5. 這是一個可怕的黑客,但它在某些情況下可能會起作用:用產生至少 4K 輸出的虛擬輸入散布您的輸入。 例如,您可以在每個實際輸入行之后輸出 2047 個空行(2047 個 CRLF 加上來自實際輸出的 CRLF = 4K),或者單行b'A' * 4092 + b'\\r\\n' ,以哪個為准快點。

前兩個答案建議的方法根本不在此列表中:將輸出定向到 Win32 控制台並抓取控制台。 這是一個糟糕的主意,因為抓取會讓您將輸出煮熟為字符的矩形數組。 刮板無法知道兩條線是否原來是一條被包裹的超長線。 如果猜錯了,您的輸出將與您的輸入不同步。 如果您完全關心輸出的完整性,則不可能以這種方式解決輸出緩沖問題。

我想答案,如果不是解決方案,可以在這里找到https://github.com/ikriv/ConsoleProxy/blob/master/src/Tools/Exec/readme.md

我想,因為我有一個類似的問題,我解決了這個問題,並且無法嘗試這條路線,因為這個工具不適用於 Windows 2003,這是我必須使用的操作系統(在遺留應用程序的 VM 中)。

我想知道我是否猜對了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM