2013-05-01 34 views
9

Có một vấn đề đã biết trong Python, trong đó "close failed in file object destructor" when "Broken pipe" happens on stdout - Python tracker Issue 11380; cũng được thấy trong python - Why does my Python3 script balk at piping its output to head or tail (sys module)? - Stack Overflow.Ngăn chặn bản in của thông báo "Ngoại lệ ... bị bỏ qua" trong Python 3

Điều tôi muốn làm là in ra cùng một thông báo tùy chỉnh khi sự cố này xảy ra, trong cả Python 2.7 và Python 3+. Vì vậy, tôi chuẩn bị một kịch bản kiểm tra, testprint.py và chạy nó (đoạn trích thể hiện thực hiện trong bash, Ubuntu 11.04):

$ cat > testprint.py <<"EOF" 
import sys 

def main(): 
    teststr = "Hello " * 5 
    sys.stdout.write(teststr + "\n") 

if __name__ == "__main__": 
    main() 
EOF 

$ python2.7 testprint.py 
Hello Hello Hello Hello Hello 

$ python2.7 testprint.py | echo 

close failed in file object destructor: 
sys.excepthook is missing 
lost sys.stderr 

$ python3.2 testprint.py | echo 

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

Đúng như dự đoán từ các liên kết trên, có hai thông điệp khác nhau. Trong Help with a piping error (velocityreviews.com), bạn nên sử dụng sys.stdout.flush() để buộc Python 2 đăng ký IOError thay vì thông báo đó; với điều đó, chúng ta có:

$ cat > testprint.py <<"EOF" 
import sys 

def main(): 
    teststr = "Hello " * 5 
    sys.stdout.write(teststr + "\n") 
    sys.stdout.flush() 

if __name__ == "__main__": 
    main() 
EOF 

$ python2.7 testprint.py | echo 

Traceback (most recent call last): 
    File "testprint.py", line 9, in <module> 
    main() 
    File "testprint.py", line 6, in main 
    sys.stdout.flush() 
IOError: [Errno 32] Broken pipe 

$ python3.2 testprint.py | echo 

Traceback (most recent call last): 
    File "testprint.py", line 9, in <module> 
    main() 
    File "testprint.py", line 6, in main 
    sys.stdout.flush() 
IOError: [Errno 32] Broken pipe 
Exception IOError: (32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored 

OK, tiến gần hơn ... Bây giờ, cách để "bỏ qua" những trường hợp ngoại lệ (hoặc trong trường hợp của tôi, thay thế bằng một thông báo lỗi tùy chỉnh), là để xử lý chúng:

Ignore exceptions - comp.lang.python

> có cách nào để làm cho [dịch] bỏ qua trường hợp ngoại lệ.
Không. Hãy xử lý các ngoại lệ hoặc viết mã không tạo ngoại lệ.

... và như An Introduction to Python - Handling Exceptions lưu ý, cách để làm điều đó là khối thử/ngoại trừ. Vì vậy, hãy cố gắng rằng:

$ cat > testprint.py <<"EOF" 
import sys 

def main(): 
    teststr = "Hello " * 5 
    try: 
    sys.stdout.write(teststr + "\n") 
    sys.stdout.flush() 
    except IOError: 
    sys.stderr.write("Exc: " + str(sys.exc_info()[0]) + "\n") 

if __name__ == "__main__": 
    main() 
EOF 

$ python2.7 testprint.py | echo 

Exc: <type 'exceptions.IOError'> 

$ python3.2 testprint.py | echo 

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

Ok, vì vậy hãy thử/trừ các công trình như tôi mong đợi nó cho Python 2.7 - nhưng sau đó, Python 3.2 cả hai tay cầm như mong đợi, và vẫn tạo ra một thông điệp Exception ... ignored! Có vấn đề gì - không phải là "except IOError" đủ cho Python 3? Nhưng nó phải được - nếu không nó sẽ không có in thông báo "Exc:..." tùy chỉnh!

Vì vậy, vấn đề ở đây là gì và tại sao Exception ... ignored vẫn được in bằng Python 3, ngay cả khi tôi đang xử lý ngoại lệ? Và quan trọng hơn, làm thế nào để xử lý nó để Exception ... ignoredkhông không được in nữa?

+0

Nó làm việc cho tôi với đầu, đuôi, ít hơn, độc đáo hơn và có vẻ như có một lỗi thực sự trong tương tác với tiếng vang nói riêng. Phần "Ngoại lệ bị bỏ qua" thực sự xảy ra trong khi tắt thông dịch khi nó cố gắng tuôn ra các luồng chuẩn một lần nữa. – ncoghlan

Trả lời

3

Chỉ cần một số lưu ý thêm về điều này - vấn đề vẫn không được giải quyết ... đầu tiên: Thông điệp

Issue 6294: Improve shutdown exception ignored message - Python tracker

Lỗi này được tạo ra trong PyErr_WriteUnraisable, đó là gọi từ nhiều bối cảnh, trong đó có phương pháp __del__ . Một phương thức __del__ được gọi là được gọi là tắt máy rất có thể là do lỗi mà bạn đang tạo ra, nhưng theo như tôi biết phương thức __del__ không có cách nào để biết rằng nó đang được gọi trong khi tắt máy nói riêng. Vì vậy, đề xuất sửa cho thư sẽ không hoạt động. [....]
Tuy nhiên, vì đây là tin nhắn bạn thậm chí không thể bẫy nó nên hoàn toàn an toàn để thay đổi.

Vâng, cảm ơn thông báo này mà bạn không thể bẫy, rất thuận tiện.Tôi tin rằng điều này bằng cách nào đó liên quan đến Ignore exceptions printed to stderr in del() - Stack Overflow, mặc dù bài đăng đó (rõ ràng) nói về các phương thức tùy chỉnh __del__.

Sử dụng một chút của các nguồn sau:

... Tôi sửa đổi kịch bản, vì vậy tôi quá tải tất cả các bộ xử lý có thể, tôi có thể, để xem nếu có không phải là một nơi nào đó nơi tôi có thể "xử lý" ngoại lệ này để nó không bị "bỏ qua":

import sys 
import atexit 
import signal 
import inspect, pprint 

def signalPIPE_handler(signal, frame): 
    sys.stderr.write('signalPIPE_handler!'+str(sys.exc_info())+'\n') 
    return #sys.exit(0) # just return doesn't exit! 
signal.signal(signal.SIGPIPE, signalPIPE_handler) 

_old_excepthook = sys.excepthook 
def myexcepthook(exctype, value, intraceback): 
    import sys 
    import traceback 
    sys.stderr.write("myexcepthook\n") 
    if exctype == IOError: 
    sys.stderr.write(" IOError intraceback:\n") 
    traceback.print_tb(intraceback) 
    else: 
    _old_excepthook(exctype, value, intraceback) 
sys.excepthook = myexcepthook 

def _trace(frame, event, arg): 
    if event == 'exception': 
    while frame is not None: 
     filename, lineno = frame.f_code.co_filename, frame.f_lineno 
     sys.stderr.write("_trace exc frame: " + filename \ 
     + " " + str(lineno) + " " + str(frame.f_trace) + str(arg) + "\n") 
     if arg[0] == IOError: 
     myexcepthook(arg[0], arg[1], arg[2]) 
     frame = frame.f_back 
    return _trace 
sys.settrace(_trace) 

def exiter(): 
    import sys 
    sys.stderr.write("Exiting\n") 
atexit.register(exiter) 

def main(): 
    teststr = "Hello " * 5 
    try: 
    sys.stdout.write(teststr + "\n") 
    sys.stdout.flush() 
    except IOError: 
    sys.stderr.write("Exc: " + str(sys.exc_info()[0]) + "\n") 
    #sys.exit(0) 


if __name__ == "__main__": 
    main() 

Lưu ý sự khác biệt trong cách kịch bản này chạy:

$ python2.7 testprint.py | echo 

signalPIPE_handler!(None, None, None) 
_trace exc frame: testprint.py 44 <function _trace at 0xb748e5dc>(<type 'exceptions.IOError'>, (32, 'Broken pipe'), <traceback object at 0xb748acac>) 
myexcepthook 
IOError intraceback: 
    File "testprint.py", line 44, in main 
    sys.stdout.flush() 
_trace exc frame: testprint.py 51 None(<type 'exceptions.IOError'>, (32, 'Broken pipe'), <traceback object at 0xb748acac>) 
myexcepthook 
IOError intraceback: 
    File "testprint.py", line 44, in main 
    sys.stdout.flush() 
Exc: <type 'exceptions.IOError'> 
Exiting 

$ python3.2 testprint.py | echo 

signalPIPE_handler!(None, None, None) 
_trace exc frame: testprint.py 44 <function _trace at 0xb74247ac>(<class 'IOError'>, (32, 'Broken pipe'), <traceback object at 0xb747393c>) 
myexcepthook 
IOError intraceback: 
    File "testprint.py", line 44, in main 
    sys.stdout.flush() 
_trace exc frame: testprint.py 51 None(<class 'IOError'>, (32, 'Broken pipe'), <traceback object at 0xb747393c>) 
myexcepthook 
IOError intraceback: 
    File "testprint.py", line 44, in main 
    sys.stdout.flush() 
Exc: <class 'IOError'> 
signalPIPE_handler!(None, None, None) 
Exiting 
signalPIPE_handler!(None, None, None) 
Exception IOError: (32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored 

Lưu ý rằng signalPIPE_handler chạy thêm hai lần trong Python 3! Tôi nghĩ rằng, nếu có một số loại "hàng đợi ngoại lệ" trong Python, tôi có thể "peek" trong nó, và loại bỏ các sự kiện còn lại trong signalPIPE_handler, để ngăn chặn thông báo Exception ... ignored ... nhưng tôi không biết bất kỳ điều như vậy.

Cuối cùng, các nguồn lực này là tốt đẹp khi cố gắng để gỡ lỗi với gdb:

... vì tôi không có python3-dbg, tất cả điều này làm giảm đến mức bước thông qua các hướng dẫn của máy (layout asm trong gdb, sau đó nhấn Ctrl-X + A), điều này thực sự không cho tôi biết nhiều. Nhưng ở đây là làm thế nào để kích hoạt các vấn đề trong gdb:

Trong một thiết bị đầu cuối:

$ mkfifo foo 
$ gdb python3.2 
... 
Reading symbols from /usr/bin/python3.2...(no debugging symbols found)...done. 
(gdb) run testprint.py > foo 
Starting program: /usr/bin/python3.2 testprint.py > foo 

Ở đây nó sẽ chặn; trong một thiết bị đầu cuối trong diretory cùng làm:

$ echo <foo 

... sau đó trở về thiết bị đầu cuối đầu tiên - bạn sẽ thấy:

... 
Starting program: /usr/bin/python3.2 testprint.py > foo 
[Thread debugging using libthread_db enabled] 
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". 

Program received signal SIGPIPE, Broken pipe. 
0x0012e416 in __kernel_vsyscall() 
(gdb) bt 
#0 0x0012e416 in __kernel_vsyscall() 
#1 0x0013c483 in __write_nocancel() from /lib/i386-linux-gnu/libpthread.so.0 
#2 0x0815b549 in ??() 
#3 0x08170507 in ??() 
#4 0x08175e43 in PyObject_CallMethodObjArgs() 
#5 0x0815df21 in ??() 
#6 0x0815f94e in ??() 
#7 0x0815fb05 in ??() 
#8 0x08170507 in ??() 
#9 0x08175cb1 in _PyObject_CallMethod_SizeT() 
#10 0x08164851 in ??() 
#11 0x080a3a36 in PyEval_EvalFrameEx() 
#12 0x080a3a53 in PyEval_EvalFrameEx() 
#13 0x080a43c8 in PyEval_EvalCodeEx() 
#14 0x080a466f in PyEval_EvalCode() 
#15 0x080c6e9d in PyRun_FileExFlags() 
#16 0x080c70c0 in PyRun_SimpleFileExFlags() 
#17 0x080db537 in Py_Main() 
#18 0x0805deee in main() 
(gdb) finish 
Run till exit from #0 0x0012e416 in __kernel_vsyscall() 
0x0013c483 in __write_nocancel() from /lib/i386-linux-gnu/libpthread.so.0 
... 

Thật không may, tôi không có khả năng để xây dựng Python3 từ nguồn và gỡ lỗi ngay bây giờ; vì vậy tôi sẽ hy vọng câu trả lời từ một người nào đó biết được :)

Chúc mừng!

nhắn
3

Lỗi này là Python chỉ ra rằng định nghĩa đường ống cung cấp bị hỏng, mặc dù theo một cách hơi khó hiểu (xem http://bugs.python.org/issue11380)

tiếng vang không thực sự chấp nhận đầu vào thông qua stdin, vì vậy các đường ống đầu vào từ Python kết thúc bị đóng cửa sớm. Ngoại lệ thêm mà bạn nhìn thấy (bên ngoài trình xử lý ngoại lệ) sau đó là do nỗ lực ngầm để xóa các luồng chuẩn khi trình thông dịch tắt. Điều này xảy ra bên ngoài phạm vi của bất kỳ người dùng nào được cung cấp mã Python, vì vậy trình thông dịch chỉ viết lỗi đến stderr thay vì gọi xử lý ngoại lệ thông thường.

Nếu bạn biết bạn không quan tâm đến các ống bị hỏng cho trường hợp sử dụng của mình, bạn có thể giải quyết trường hợp này bằng cách đóng một cách rõ ràng stdout trước khi kết thúc chương trình của bạn. Nó vẫn sẽ phàn nàn về các đường ống bị hỏng, nhưng nó sẽ làm điều đó theo một cách mà cho phép bạn nắm bắt và ngăn chặn những ngoại lệ như thường lệ:

import sys 

def main(): 
    teststr = "Hello " * 5 
    try: 
    sys.stdout.write(teststr + "\n") 
    sys.stdout.flush() 
    except IOError: 
    sys.stderr.write("Exc: " + str(sys.exc_info()[0]) + "\n") 
    try: 
    sys.stdout.close() 
    except IOError: 
    sys.stderr.write("Exc on close: " + str(sys.exc_info()[0]) + "\n") 

if __name__ == "__main__": 
    main() 

Trong phiên bản này, chỉ có kết quả mong muốn được nhìn thấy, bởi vì ngay cả những nỗ lực tại đóng cửa nó là đủ để đảm bảo dòng đã được đánh dấu là đóng cửa trong phiên dịch shutdown:

$ python3 testprint.py | echo 

Exc: <class 'BrokenPipeError'> 
Exc on close: <class 'BrokenPipeError'> 
0

Đây là một hack RẤT xấu xí để ngăn chặn các thông báo lỗi được hiển thị trong trường hợp in để stdout gây ra một đường ống bị hỏng (ví dụ vì quá trình máy nhắn tin được gọi như your-program.py | less đã bị hủy mà không cần cuộn xuống cuối đầu ra:

try: 
    actual_code() 
except BrokenPipeError: 
    sys.stdout = os.fdopen(1) 
Các vấn đề liên quan