2009-07-24 37 views
26

Tôi có một số mã Python thực thi một ứng dụng bên ngoài hoạt động tốt khi ứng dụng có một lượng nhỏ đầu ra, nhưng bị treo khi có rất nhiều. Mã của tôi trông giống như:Sử dụng subprocess.Popen cho quá trình với đầu ra lớn

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
errcode = p.wait() 
retval = p.stdout.read() 
errmess = p.stderr.read() 
if errcode: 
    log.error('cmd failed <%s>: %s' % (errcode,errmess)) 

Có ý kiến ​​trong tài liệu dường như cho biết vấn đề tiềm ẩn. Dưới chờ đợi, có:

Cảnh báo: Đây sẽ bế tắc nếu quá trình con tạo ra đủ sản lượng để một ống stdout hoặc stderr như vậy mà nó khối chờ đợi bộ đệm ống OS để chấp nhận dữ liệu hơn. Sử dụng communicate() để tránh điều đó.

dù dưới giao tiếp, tôi thấy:

Lưu ý Các dữ liệu đọc được đệm trong bộ nhớ, do đó, không sử dụng phương pháp này nếu kích thước dữ liệu quá lớn hoặc không giới hạn.

Vì vậy, không rõ ràng với tôi rằng tôi nên sử dụng một trong những điều này nếu tôi có một lượng lớn dữ liệu. Họ không chỉ ra phương pháp nào tôi nên sử dụng trong trường hợp đó.

Tôi cần giá trị trả lại từ lệnh exec và phân tích cú pháp và sử dụng cả hai stdoutstderr.

Vì vậy, một phương pháp tương đương trong Python để thực thi một ứng dụng bên ngoài sẽ có đầu ra lớn là gì?

+3

Có vẻ như "lớn" trong giao tiếp tài liệu là * lớn hơn nhiều * hơn bạn có khả năng chờ đợi, và chắc chắn lớn hơn nhiều so với thông thường. Ví dụ, bạn có thể xuất 10MB văn bản và hầu hết các hệ thống sẽ ổn với giao tiếp. Đầu ra của 1GB khi bạn chỉ có 1GB RAM sẽ là một câu chuyện khác. –

Trả lời

16

Bạn đang chặn việc đọc tới hai tệp; nhu cầu đầu tiên cần hoàn thành trước khi bắt đầu lần thứ hai. Nếu ứng dụng viết rất nhiều đến stderr, và không có gì để stdout, sau đó quá trình của bạn sẽ ngồi chờ đợi dữ liệu trên stdout mà không đến, trong khi chương trình bạn đang ngồi ngồi chờ đợi cho những thứ nó đã viết để stderr để được đọc (mà nó sẽ không bao giờ - vì bạn đang đợi stdout).

Có một số cách bạn có thể khắc phục điều này.

Cách đơn giản nhất là không chặn stderr; rời khỏi stderr=None. Lỗi sẽ được xuất trực tiếp đến stderr. Bạn không thể chặn chúng và hiển thị chúng như là một phần của thông điệp của riêng bạn. Đối với các công cụ dòng lệnh, điều này thường là OK. Đối với các ứng dụng khác, nó có thể là một vấn đề.

Một cách tiếp cận đơn giản khác là chuyển hướng stderr đến stdout, vì vậy bạn chỉ có một tệp đến: đặt stderr=STDOUT. Điều này có nghĩa là bạn không thể phân biệt đầu ra thường xuyên từ đầu ra lỗi. Điều này có thể hoặc có thể không được chấp nhận, tùy thuộc vào cách ứng dụng ghi đầu ra.

Cách xử lý hoàn chỉnh và phức tạp này là select (http://docs.python.org/library/select.html). Điều này cho phép bạn đọc theo cách không chặn: bạn nhận dữ liệu bất cứ khi nào dữ liệu xuất hiện trên stdout hoặc stderr. Tôi chỉ đề nghị điều này nếu nó thực sự cần thiết.Điều này có thể không hoạt động trong Windows.

+0

Vì trường hợp cụ thể mà tôi đang xử lý sẽ có rất nhiều stdout và một lượng nhỏ hoặc không có stderr, tôi sẽ thử chuyển hướng tệp được đề xuất đầu tiên, nhưng phạm vi phủ sóng đầy đủ hơn về vấn đề này rất hữu ích. – Tim

6

Rất nhiều đầu ra là chủ quan nên hơi khó để đưa ra đề xuất. Nếu số lượng đầu ra là thực sự là lớn thì bạn có thể không muốn lấy tất cả chỉ bằng một lệnh gọi đọc(). Bạn có thể muốn thử viết đầu ra vào một tệp và sau đó kéo dữ liệu theo từng bước như vậy:

f=file('data.out','w') 
p = subprocess.Popen(cmd, shell=True, stdout=f, stderr=subprocess.PIPE) 
errcode = p.wait() 
f.close() 
if errcode: 
    errmess = p.stderr.read() 
    log.error('cmd failed <%s>: %s' % (errcode,errmess)) 
for line in file('data.out'): 
    #do something 
+2

Điều này cũng có thể dễ dàng bế tắc. Nếu quá trình chia nhỏ ghi nhiều dữ liệu hơn hệ điều hành sẽ đệm vào stderr trước khi thoát với một mã lỗi, mã này sẽ ngồi mãi mãi chờ nó thoát, trong khi quá trình nằm trên một ghi chặn để stderr chờ đợi cho bạn để đọc nó. –

+0

1) giả định đầu ra dữ liệu lớn là stderr mà sẽ là kỳ lạ nhưng không nghe thấy), 2) nếu stderr IS là nguồn dữ liệu lớn, dung lượng là giống nhau, làm cho tệp stderr cũng là –

+0

Trong trường hợp này, quá trình có thể có khả năng có rất nhiều stdout, nhưng sẽ không có nhiều, nếu có, stderr, vì vậy đây là một giải pháp hợp lý cho tôi. – Tim

2

Bạn có thể thử giao tiếp và xem cách đó giải quyết được sự cố của bạn. Nếu không, tôi sẽ chuyển hướng đầu ra sang một tệp tạm thời.

+0

Vì giao tiếp rõ ràng sẽ cảnh báo tránh xa việc sử dụng nếu bạn có rất nhiều đầu ra, tôi sẽ xem xét các tùy chọn khác. – Tim

6

Glenn Maynard là đúng trong nhận xét của mình về deadlocks. Tuy nhiên, cách tốt nhất để giải quyết vấn đề này là tạo hai chủ đề, một cho stdout và một cho stderr, đọc các luồng tương ứng cho đến khi kiệt sức và làm bất cứ điều gì bạn cần với đầu ra.

Đề xuất sử dụng tệp tạm thời có thể hoặc có thể không hoạt động cho bạn tùy thuộc vào kích thước đầu ra, v.v. và bạn có cần xử lý đầu ra của tiến trình con khi nó được tạo ra hay không.

Như Heikki Toivonen đã đề xuất, bạn nên xem phương thức communicate. Tuy nhiên, điều này đệm stdout/stderr của subprocess trong bộ nhớ và bạn nhận được những trở về từ cuộc gọi communicate - điều này không lý tưởng cho một số kịch bản. Nhưng nguồn của phương thức giao tiếp đáng xem.

Một ví dụ khác là trong một gói tôi duy trì, python-gnupg, nơi gpg thực thi được sinh ra qua subprocess để làm việc nặng nhọc, và wrapper Python sinh ra các luồng để đọc stdout và stderr gpg và tiêu thụ chúng như dữ liệu được tạo ra bởi gpg . Bạn cũng có thể nhận được một số ý tưởng bằng cách nhìn vào nguồn ở đó. Dữ liệu do gpg tạo ra cho cả stdout và stderr có thể khá lớn, trong trường hợp chung.

+0

Sẽ kiểm tra python-gnupg làm ví dụ. Cảm ơn. – Tim

+1

Các liên kết có liên quan đến các phương pháp thú vị - ['_open_subprocess'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg. py-825) và ['_collect_output'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg.py-903) – neowulf33

1

Tôi cũng gặp vấn đề tương tự. Nếu bạn phải xử lý một đầu ra lớn, một lựa chọn tốt khác có thể là sử dụng một tệp cho stdout và stderr, và chuyển các tệp đó cho mỗi tham số.

Kiểm tra mô-đun tempfile trong python: https://docs.python.org/2/library/tempfile.html.

Something như thế này có thể làm việc

out = tempfile.NamedTemporaryFile(delete=False) 

Sau đó, bạn sẽ làm gì:

Popen(... stdout=out,...) 

Sau đó, bạn có thể đọc các tập tin, và xóa nó sau này.

5

Reading stdoutstderr một cách độc lập với sản lượng rất lớn (ví dụ, rất nhiều MB) sử dụng select:

import subprocess, select 

proc = subprocess.Popen(cmd, bufsize=8192, shell=False, \ 
    stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

with open(outpath, "wb") as outf: 
    dataend = False 
    while (proc.returncode is None) or (not dataend): 
     proc.poll() 
     dataend = False 

     ready = select.select([proc.stdout, proc.stderr], [], [], 1.0) 

     if proc.stderr in ready[0]: 
      data = proc.stderr.read(1024) 
      if len(data) > 0: 
       handle_stderr_data(data) 

     if proc.stdout in ready[0]: 
      data = proc.stdout.read(1024) 
      if len(data) == 0: # Read of zero bytes means EOF 
       dataend = True 
      else: 
       outf.write(data) 
+0

Điều này cho đến nay có ý nghĩa nhất đối với tôi khi khắc phục các vấn đề trong bộ nhớ đệm. Tôi thậm chí đã thử subprocess 'cmd' là' bash -c 'cat/dev/urandom | tr -dc' a-zA-Z0-9 '"' hoạt động tốt. Khối tâm thần của tôi là những gì có nghĩa là - [1] 'sẵn sàng [0]' và tại sao [2] 'len (proc.stdout.read (1024)) == 0' có nghĩa là EOF? [3] Tại sao không kiểm tra 'len (proc.stderr.read (1024))'? [4] Tại sao đọc tuôn ra không cần thiết? Rất tiếc, một số câu hỏi được gộp thành một nhận xét:/ – neowulf33

+1

@ neowulf33 [1] sẵn sàng là danh sách các danh sách, sẵn sàng [0] là danh sách có thể chứa stdout, stderr hoặc cả hai. xem tài liệu được chọn. [2] "Một chuỗi rỗng được trả về khi EOF gặp phải ngay lập tức." https://docs.python.org/2.7/library/stdtypes.html#file.read [3] vì bạn sẽ mất dữ liệu! [4] Tôi không hiểu, tuôn ra như thế nào? – vz0

+0

Cảm ơn! Tệ của tôi - tôi chắc chắn đã ngủ khi tôi viết "đọc xả"! – neowulf33

Các vấn đề liên quan