2013-05-01 6 views
9

Znany jest problem w Pythonie, gdzie "close failed in file object destructor" when "Broken pipe" happens on stdout - Python tracker Issue 11380; również widoczne w python - Why does my Python3 script balk at piping its output to head or tail (sys module)? - Stack Overflow.Pomijanie wydruku komunikatu "Wyjątek ... ignorowane" w Pythonie 3

To, co chcę zrobić, to wydrukowanie tego samego niestandardowego komunikatu, gdy wystąpi ten problem, zarówno w Pythonie 2.7, jak i w Pythonie 3+. Więc przygotować skrypt testowy, testprint.py i uruchom go (Pokazane fragmenty wykonane w 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 

Zgodnie z oczekiwaniami z linków powyżej, istnieją dwa różne komunikaty. W Help with a piping error (velocityreviews.com) zaleca się użycie sys.stdout.flush(), aby zmusić Python 2 do zarejestrowania IOError zamiast tego komunikatu; z tym, mamy:

$ 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, coraz bliżej ... Teraz, droga do „ignoruj” te wyjątki (lub w moim przypadku, wymienić z komunikatem błędu niestandardowego), ma je obsługiwać:

Ignore exceptions - comp.lang.python

> Czy istnieje jakiś sposób, aby [tłumacza] ignorować wyjątki.
Nie. Obsługuj wyjątki lub pisz kod, który nie generuje wyjątków.

... a jako An Introduction to Python - Handling Exceptions zauważyłem, że sposobem na to jest blok try/except. Więc spróbujmy, że:

$ 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, więc spróbuj/z wyjątkiem prac jak oczekuję go dla Pythona 2.7 - ale wtedy, Python 3.2 oba uchwyty zgodnie z oczekiwaniami, i nadal generuje komunikat Exception ... ignored! W czym tkwi problem - czy "Python 3" nie jest "except IOError"? Ale musi tak być - w przeciwnym razie nie wydrukowałby niestandardowej wiadomości "Exc:..."!

A więc - jaki jest tutaj problem i dlaczego Exception ... ignored nadal jest drukowany w Pythonie 3, nawet jeśli obsługuję wyjątek? I co ważniejsze, jak mam sobie z tym poradzić, aby drukować więcej niż 1?.

+0

Działa na mnie z głową, ogonem, mniej, więcej i niepowtarzalnie, więc wygląda na to, że istnieje konkretny błąd w interakcji z echo w szczególności. Część "Zignorowany wyjątek" faktycznie dzieje się podczas wyłączania interpretera, gdy próbuje ponownie wypłukać standardowe strumienie. – ncoghlan

Odpowiedz

3

Zaledwie kilka uwag o tym - problem nadal nie rozwiązany ... po pierwsze: Komunikat

Issue 6294: Improve shutdown exception ignored message - Python tracker

Ten błąd jest generowany w PyErr_WriteUnraisable, który jest wywoływana z wielu kontekstach, w tym metod __del__ . Metoda __del__ wywołana podczas wyłączania jest najprawdopodobniej powodem generowania błędu, o którym mówisz , ale o ile mi wiadomo, metoda __del__ nie ma możliwości, aby wiedzieć, że jest wywoływana w szczególności podczas zamykania. Proponowana poprawka do wiadomości nie będzie działać. [....]
Jednak ponieważ jest to wiadomość, której nie można nawet uwięzić, powinno być całkowicie bezpieczne, aby ją zmienić.

Cóż, dziękuję za tę wiadomość, której nie można przechwycić, bardzo wygodne.Wierzę, że jest to w jakiś sposób związane z Ignore exceptions printed to stderr in del() - Stack Overflow, chociaż ten post (podobno) mówi o niestandardowych metodach __del__.

Korzystanie trochę następujących środków:

... I zmodyfikowany skrypt, więc przeładowywać wszystkie możliwe teleskopowe I Can, aby sprawdzić, czy nie jest miejscem gdzie mogę "obsłużyć" ten wyjątek, więc nie jest "ignorowany":

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() 

zauważyć różnicę w tym, jak ten skrypt działa:

$ 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 

Zauważ, że signalPIPE_handler biegnie dwa razy więcej w Pythonie 3! Myślę, że gdyby w Pythonie istniała jakaś "kolejka wyjątków", mógłbym "zerknąć" w nią i usunąć pozostałe zdarzenia z signalPIPE_handler, aby wyłączyć komunikat Exception ... ignored ... ale nie znam żadnej taka rzecz.

Wreszcie, zasoby te są ładne podczas próby debugowania z gdb:

... ponieważ nie mam python3-dbg, to wszystko sprowadza się do intensyfikacji poprzez instrukcje maszynowe (layout asm w gdb, następnie Ctrl-X + A), które tak naprawdę niewiele mi mówią. Ale tutaj jest jak wywołać problem w gdb:

W jednym terminalu:

$ 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 

Tutaj będzie blokować; w innym terminalu w tym samym diretory zrobić:

$ echo <foo 

... następnie powrócić do pierwszego terminalu - powinieneś zobaczyć:

... 
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 
... 

Niestety, nie mam możliwości, aby zbudować Python3 ze źródła i debuguj go teraz; więc mam nadzieję na odpowiedź od kogoś, kto zna :)

Pozdrawiam!

wiadomość
3

Błąd ten wskazuje, że Python dostarczany definicja rurociąg jest uszkodzony, aczkolwiek w nieco mylący sposób (patrz http://bugs.python.org/issue11380)

echo rzeczywistości nie akceptuje wejście za pośrednictwem standardowego wejścia, więc rura wejście od Python kończy się zamykanie wcześnie. Dodatkowy wyjątek, który widzisz (poza obsługą wyjątku) jest spowodowany domyślną próbą wypłukania standardowych strumieni podczas zamykania interpretera. Dzieje się tak poza zakresem jakiegokolwiek kodu Pythona dostarczonego przez użytkownika, więc interpreter po prostu zapisuje błąd do stderr zamiast wywoływać normalne przetwarzanie wyjątku.

Jeśli wiesz, że nie zależy Ci na zepsutych rurach w twoim przypadku użycia, możesz poradzić sobie z tym przypadkiem, zamykając jawnie stdout przed końcem programu. Będzie on nadal narzekają na złamaną rury, ale będzie to robić w sposób, który pozwala złapać i tłumić wyjątek jak zwykle:

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() 

W tej wersji, tylko oczekiwany wynik widać, bo nawet próby w zamykania, wystarczy do zapewnienia strumienia już zaznaczono jako zamknięte podczas zamykania tłumacza:

$ python3 testprint.py | echo 

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

to bardzo brzydki Hack tłumić wiadomość o błędzie jest pokazana, w przypadku drukowania na standardowe spowodowane pęknięcia przewodu (np ponieważ proces przywoływania, taki jak your-program.py | less, został zakończony bez przewijania do dolnej części wyjścia:

try: 
    actual_code() 
except BrokenPipeError: 
    sys.stdout = os.fdopen(1)