2012-01-23 30 views
10

Có cách nào để thực hiện cuộc gọi subprocess trong python "persistent" không? Tôi đang gọi một chương trình mất nhiều thời gian để tải nhiều lần. Vì vậy, nó sẽ là tuyệt vời nếu tôi chỉ có thể rời khỏi chương trình đó mở và giao tiếp với nó mà không giết chết nó.Quy trình con trăn liên tục

Phiên bản phim hoạt hình của kịch bản python của tôi trông như thế này:

for text in textcollection: 
    myprocess = subprocess.Popen(["myexecutable"], 
       stdin = subprocess.PIPE, stdout = subprocess.PIPE, 
       stderr = None) 
    myoutputtext, err = myprocess.communicate(input=text) 

tôi cần phải xử lý từng văn bản riêng biệt, vì vậy khi tham gia tất cả vào một tập tin văn bản lớn và xử lý nó một lần không phải là một lựa chọn.

Tốt, nếu có một lựa chọn như thế này

myprocess = subprocess.Popen(["myexecutable"], 
      stdin = subprocess.PIPE, stdout = subprocess.PIPE, 
      stderr = None) for text in textcollection: 
for text in textcollection: 
    myoutputtext, err = myprocess.communicate(input=text) 

nơi tôi có thể rời khỏi quá trình mở, tôi thực sự đánh giá cao nó.

Trả lời

24

Bạn có thể sử dụng myprocess.stdin.write()myprocess.stdout.read() để giao tiếp với quy trình con của bạn, bạn chỉ cần cẩn thận để đảm bảo bạn xử lý đệm đúng cách để ngăn chặn cuộc gọi của bạn chặn.

Nếu đầu ra từ quy trình con của bạn được xác định rõ, bạn sẽ có thể liên lạc đáng tin cậy với nó bằng cách sử dụng bộ đệm dòng và myprocess.stdout.readline().

Dưới đây là một ví dụ:

>>> p = subprocess.Popen(['cat'], bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
>>> p.stdin.write('hello world\n') 
>>> p.stdout.readline() 
'hello world\n' 
>>> p.stdout.readline()  # THIS CALL WILL BLOCK 

Một thay thế cho phương pháp này cho Unix là để đặt các tập tin xử lý trong non-blocking mode, mà sẽ cho phép bạn để gọi các chức năng như myprocess.stdout.read() và có nó trả lại dữ liệu nếu bất kỳ có sẵn, hoặc huy động một IOError nếu không có bất kỳ dữ liệu:

>>> p = subprocess.Popen(['cat'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
>>> import fcntl, os 
>>> fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 
0 
>>> p.stdout.read()   # raises an exception instead of blocking 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
IOError: [Errno 11] Resource temporarily unavailable 

này sẽ cho phép bạn làm điều gì đó như thế này:

fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 
for text in textcollection: 
    myprocess.stdin.write(text + '\n') 
    while True: 
     myoutputtext = '' 
     try: 
      myoutputtext += myprocess.stdout.read() 
     except IOError: 
      pass 
     if validate_output(myoutputtext): 
      break 
     time.sleep(.1) # short sleep before attempting another read 

Trong ví dụ này, validate_output() là một hàm bạn cần viết trả về True nếu dữ liệu bạn đã nhận được cho đến nay là tất cả đầu ra mà bạn mong muốn nhận được.

+1

Cảm ơn! Tôi thích giải pháp của bạn tốt nhất vì nó không yêu cầu tải xuống của bên thứ ba. Thật không may, nó không làm việc cho tôi. Sau khi thử một vài điều, tôi khá chắc chắn đó là một vấn đề với chương trình java tôi gọi thay vì giải pháp của bạn, vì vậy giải pháp của bạn là tốt. – JasonMond

+0

Tại sao bỏ phiếu xuống? –

+0

Điều này là do nhầm lẫn. Upvote của tôi là không hoạt động cho đến khi bất cứ điều gì sẽ được chỉnh sửa, nhưng tôi không thấy bất cứ điều gì để cải thiện hoặc không bị tổn thương. Câu trả lời hoàn hảo. – hynekcer

1

Tôi nghĩ rằng bạn đang tìm kiếm

myprocess.stdin.write(text) 

bạn có thể tạo một danh sách các Popens và sau đó gọi giao tiếp trên mỗi phần tử trong vòng lặp khác. một cái gì đó như thế này

processes=[] 
for text in textcollection: 
    myprocess = subprocess.Popen(["myexecutable"], 
       stdin = subprocess.PIPE, stdout = subprocess.PIPE, 
       stderr = None) 
    myprocess.stdin.write(text) 
    processes.append(myprocess) 

for proc in processes: 
    myoutput, err=proc.communicate() 
    #do something with the output here 

cách này nó sẽ không phải đợi đến sau khi tất cả các Popens đã bắt đầu

+0

Thật không may, điều này sẽ không hiệu quả đối với tôi vì đó là một chương trình java ăn khoảng 3G bộ nhớ mỗi lần chạy. Đây là lý do tại sao phải mất quá nhiều thời gian để tải. Tôi không thể có 5000 trường hợp của một quá trình 3G. – JasonMond

+0

Tôi nghĩ tôi hiểu. Sau khi nó nhận được văn bản đầu vào, nó xuất ra một cái gì đó và sau đó thoát ra? hoặc nó chờ đợi cho bạn để nhập cái gì khác –

+0

Nó kết quả đầu ra sau đó thoát. – JasonMond

5

Đây là lời kêu gọi communicate() đang giết chết tiến trình con của bạn. Theo phương thức subprocess documentation, phương thức communicate() sẽ:

Tương tác với quy trình: Gửi dữ liệu đến stdin. Đọc dữ liệu từ stdout và stderr, cho đến khi kết thúc tập tin. Chờ quá trình chấm dứt.

Những gì bạn muốn làm là tương tác trực tiếp với các đối tượng POpen của stdinstdout tính trực tiếp để giao tiếp với các tiến trình con. Tuy nhiên, tài liệu hướng dẫn chống lại câu nói này:

Cảnh báo: Sử dụng giao tiếp() thay vì .stdin.write, .stdout.read hoặc .stderr.read để tránh deadlocks do bất kỳ bộ đệm ống hệ điều hành nào khác lập và chặn tiến trình con.

Vì vậy, bạn cần phải thực hiện các cách giải quyết của riêng bạn cho những nguy cơ tiềm ẩn hoặc hy vọng ai đó đã viết asynchronous subprocess module cho bạn.

Edit: Dưới đây là một ví dụ về cách quick'n'dirty module subprocess không đồng bộ có thể được sử dụng:

import asyncsubprocess 

textcollection = ['to', 'be', 'or', 'not', 'to be', 'that is the', 'question'] 

myprocess = asyncsubprocess.Popen(["cat"], 
    stdin = asyncsubprocess.PIPE, 
    stdout = asyncsubprocess.PIPE, 
    stderr = None) 

for text in textcollection: 
    bytes_sent, myoutput, err = myprocess.listen(text) 
    print text, bytes_sent, myoutput, err 

Khi tôi chạy, nó in:

to 2 to 
be 2 be 
or 2 or 
not 3 not 
to be 5 to be 
that is the 11 that is the 
question 8 question 
-2
if os.name == 'nt': 
startupinfo = subprocess.STARTUPINFO() 
startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW 
subprocess.call(os.popen(tempFileName), shell=True) 
os.remove(tempFileName) 
Các vấn đề liên quan