2014-11-01 52 views
14

Câu hỏi: Có cách nào để sử dụng flush=True cho hàm print() mà không nhận được BrokenPipeError không?BrokenPipeError trong Python

Tôi có một kịch bản pipe.py:

for i in range(4000): 
    print(i) 

tôi gọi nó như thế này từ một dòng lệnh Unix:

python3 pipe.py | head -n3000 

Và nó sẽ trả về:

0 
1 
2 

Vì vậy, hiện kịch bản này :

import sys 
for i in range(4000): 
    print(i) 
    sys.stdout.flush() 

Tuy nhiên, khi tôi chạy kịch bản này và ống nó để head -n3000:

for i in range(4000): 
    print(i, flush=True) 

Sau đó, tôi nhận được lỗi này:

print(i, flush=True) 
BrokenPipeError: [Errno 32] Broken pipe 
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored 

Tôi cũng đã thử các giải pháp dưới đây, nhưng tôi vẫn nhận được BrokenPipeError:

import sys 
for i in range(4000): 
    try: 
     print(i, flush=True) 
    except BrokenPipeError: 
     sys.exit() 
+0

Tôi không thể tạo lại nó trên OS X 10.1 0, hãy thử CentOS 6.6 ngay bây giờ. – jgritty

+0

Tôi vừa thử trên OS X 10.9.4 và tôi không thể tái tạo nó. Tôi gặp lỗi trên Ubuntu 12.04.2 LTS. Tôi sẽ thử Linux Mint Qiana. –

+0

Tất cả các tập lệnh của bạn đều bị vỡ, trừ trường hợp đầu tiên ... – phantom

Trả lời

13

các BrokenPipeError là bình thường như đã nói phantom vì quá trình đọc (đầu) chấm dứt và đóng phần cuối của đường ống trong khi writi ng quá trình (python) vẫn cố gắng để viết.

một điều kiện bất thường, và các kịch bản python nhận BrokenPipeError - chính xác hơn, trình thông dịch Python nhận được một tín hiệu hệ thống SIGPIPE rằng nó bắt và làm tăng BrokenPipeError để cho phép các kịch bản để xử lý các lỗi.

Và bạn có thể xử lý lỗi một cách hiệu quả, vì trong ví dụ cuối cùng, bạn chỉ thấy thông báo cho biết ngoại lệ đã bị bỏ qua - ok nó không đúng, nhưng có vẻ liên quan đến số này open issue bằng Python: Python developpers nghĩ rằng quan trọng cảnh báo người dùng về tình trạng bất thường.

Điều thực sự xảy ra là AFAIK trình thông dịch python luôn luôn báo hiệu điều này trên stderr, ngay cả khi bạn bắt ngoại lệ. Nhưng bạn chỉ cần đóng stderr trước khi thoát khỏi để thoát khỏi tin nhắn.

Tôi hơi thay đổi kịch bản của bạn:

  • bắt lỗi như bạn đã làm trong ví dụ cuối cùng của bạn
  • bắt một trong hai IOError (mà tôi nhận được trong Python34 trên Windows64) hoặc BrokenPipeError (bằng Python 33 trên FreeBSD 9.0) - và hiển thị một thông điệp cho rằng
  • hiển thị một tùy chỉnh Xong nhắn trên stderr (stdout được đóng cửa do các đường ống bị hỏng)
  • stderr gần trước khi thoát để thoát khỏi thông điệp

Đây là kịch bản tôi đã sử dụng:

import sys 

try: 
    for i in range(4000): 
      print(i, flush=True) 
except (BrokenPipeError, IOError): 
    print ('BrokenPipeError caught', file = sys.stderr) 

print ('Done', file=sys.stderr) 
sys.stderr.close() 

và đây là kết quả của python3.3 pipe.py | head -10:

0 
1 
2 
3 
4 
5 
6 
7 
8 
9 
BrokenPipeError caught 
Done 

Nếu bạn không muốn những thông điệp không liên quan chỉ cần sử dụng:

import sys 

try: 
    for i in range(4000): 
      print(i, flush=True) 
except (BrokenPipeError, IOError): 
    pass 

sys.stderr.close() 
+0

Hoàn toàn rất tuyệt vời :) Cảm ơn bạn! Cũng không biết bạn có thể đặt ngoại lệ trong một tuple. Cảm ơn vì đã cho tôi thấy điều đó. Điều này làm việc cho 3.3.2 và 3.4.0 trên Mac OS X 10.9.4 và Ubuntu 12.04.2 LTS (tất cả 4 kết hợp). Dường như nó hoạt động với cả BrokenPipeError và IOError; cả hai phần của OSError trong the exception hierarchy. –

4

Theo tài liệu Python, điều này được đưa ra khi :

trying to write on a pipe while the other end has been closed

Điều này là do thực tế là các tiện ích đầu đọc từ stdout, sau đó nhanh chóng đóng cửa nó.

Như bạn thấy, nó có thể được làm việc xung quanh bằng cách chỉ thêm sys.stdout.flush() sau mỗi print(). Lưu ý rằng điều này đôi khi không hoạt động bằng Python 3.

Bạn có thể cách khác ống nó để awk như thế này để có được những kết quả tương tự như head -3:

python3 0to3.py | awk 'NR >= 4 {exit} 1' 

Hy vọng điều này giúp, chúc may mắn!

+0

Cảm ơn vì điều này. Thật không may giải pháp thay thế awk không phải là một lựa chọn. Tôi không biết có bao nhiêu dòng sản lượng thực tế tạo ra. Tôi sẽ để lại câu hỏi chưa được trả lời trong vài ngày, nếu bạn không phiền. Cảm ơn một lần nữa. –

+0

@ tommy.carstensen Có 'sys.stdout.flush()' không hoạt động? Ngoài ra, với 'đầu' không bạn cũng phải biết có bao nhiêu dòng nó sẽ được anyway? – phantom

+0

@ tommy.carstensen Không thể làm những gì bạn đang yêu cầu. Không có cách giải quyết nào để cho phép 'head' hoạt động với chương trình của bạn theo cách này. – phantom

1

Như bạn có thể nhìn thấy trong đầu ra mà bạn đã đăng ngoại trừ cuối cùng được nâng lên trong giai đoạn destructor: đó là lý do tại sao bạn có ignored vào cuối

Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored 

Một ví dụ đơn giản để hiểu có chuyện gì thế ở chỗ bối cảnh là những sau:

>> class A(): 
...  def __del__(self): 
...   raise Exception("It will be ignored!!!") 
... 
>>> a = A() 
>>> del a 
Exception Exception: Exception('It will be ignored!!!',) in <bound method A.__del__ of <__builtin__.A instance at 0x7ff1d5c06d88>> ignored 
>>> a = A() 
>>> import sys 
>>> sys.stderr.close() 
>>> del a 

Mỗi ngoại lệ mà được kích hoạt khi đối tượng bị phá hủy sẽ gây ra một sản lượng sai số chuẩn để giải thích các ngoại lệ xảy ra và lờ đi (đó là bởi vì trăn sẽ thông báo cho bạn rằng một cái gì đó không thể b e xử lý đúng trong giai đoạn phá hủy). Dù sao, loại ngoại lệ đó không thể được lưu trong bộ nhớ cache và vì vậy bạn chỉ có thể xóa các cuộc gọi có thể tạo ra hoặc đóng stderr.

Quay lại câu hỏi.Ngoại lệ đó không phải là một vấn đề thực sự (như nói nó bị bỏ qua) nhưng nếu bạn không muốn in nó, bạn phải ghi đè lên hàm có thể được gọi khi đối tượng đó bị hủy hoặc đóng stderr vì @SergeBallesta được đề xuất chính xác: trong bạn trường hợp bạn có thể shutdownwriteflush chức năng và không có ngoại lệ sẽ được kích hoạt trong phá hủy bối cảnh

đó là một ví dụ về cách bạn có thể làm điều đó:

import sys 
def _void_f(*args,**kwargs): 
    pass 

for i in range(4000): 
    try: 
     print(i,flush=True) 
    except (BrokenPipeError, IOError): 
     sys.stdout.write = _void_f 
     sys.stdout.flush = _void_f 
     sys.exit() 
+0

Đây không phải là một giải pháp. Ông muốn để có thể sử dụng nó với 'tuôn ra = True' với' in'. – phantom

+0

Như tôi viết tôi thử nghiệm nó trên python 3.2 ... bây giờ tôi thích ứng nó với python 3.4 –

+0

@phantom Đó là phiên bản cho 'flush = True' trong' print' ... Đó là một giải pháp phải không? –