2014-11-01 19 views
14

Pytanie: Czy istnieje sposób użycia flush=True dla funkcji print() bez uzyskania BrokenPipeError?BrokenPipeError in Python

Mam skrypt pipe.py:

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

nazywam to tak z linii poleceń systemu UNIX:

python3 pipe.py | head -n3000 

I powraca:

0 
1 
2 

Więc robi ten skrypt :

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

Jednak, kiedy uruchomić ten skrypt i rury go do head -n3000:

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

Potem dostaję ten błąd:

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 

Próbowałem również rozwiązanie poniżej, ale wciąż uzyskać BrokenPipeError:

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

Nie mogę odtworzyć go na OS X 10.1 0, próbując centOS 6.6 teraz. – jgritty

+0

Po prostu próbowałem na OS X 10.9.4 i nie byłem w stanie go odtworzyć. Mam błąd w systemie Ubuntu 12.04.2 LTS. Spróbuję na Linux Mint Qiana. –

+0

Wszystkie swoje skrypty łamią się dla mnie, z wyjątkiem pierwszego ... – phantom

Odpowiedz

13

BrokenPipeError jest normalne jak powiedział fantom, ponieważ proces czytania (head) kończy się i zamyka koniec rury podczas pisania ng process (python) nadal próbuje pisać.

Czy jest nienormalny stan i skrypty python otrzyma BrokenPipeError - bardziej dokładnie, interpreter Pythona odbiera sygnał System SIGPIPE że łapie i podnosi BrokenPipeError aby umożliwić skrypt do przetwarzania błędu.

Skutecznie można przetworzyć błąd, ponieważ w twoim ostatnim przykładzie widzisz tylko komunikat informujący, że wyjątek został zignorowany - ok, nie jest to prawda, ale wydaje się być związany z tym open issue w Pythonie: Twórcy Pythona uważają za ważne ostrzec użytkownika o nienormalnym stanie.

Co naprawdę się dzieje, to to, że AFAIK interpreter python zawsze sygnalizuje to na stderr, nawet jeśli złapiesz wyjątek. Ale przed wyjściem musisz zamknąć stderr, aby pozbyć się wiadomości.

ja nieznacznie zmienił swój skrypt do:

  • połowu błędu jak to było w poprzednim przykładzie
  • haczyk albo IOError (które mam w Python34 na Windows64) lub BrokenPipeError (w Pythonie 33 na FreeBSD 9,0) - i wyświetlenie tego komunikatu na ekranie
  • zwyczaj Sporządzono wiadomość stderr (standardowe wyjście jest zamknięty ze względu na pęknięcia przewodu)
  • blisko stderr przed wyjściem, aby pozbyć się komunikatu

Oto skrypt użyłem:

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

i tu wynik python3.3 pipe.py | head -10:

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

Jeśli nie chcesz obcych wiadomości po prostu użyj:

import sys 

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

sys.stderr.close() 
+0

Absolutnie bardzo fajnie :) Dziękujemy! Również nie wiedziałeś, że możesz umieścić wyjątki w krotce. Dzięki za pokazanie mi tego również. Działa to w wersjach 3.3.2 i 3.4.0 na Mac OS X 10.9.4 i Ubuntu 12.04.2 LTS (wszystkie 4 kombinacje). Wygląda na to, że działa zarówno z BrokenPipeError, jak iz IOError na własną rękę; obie części OSError w the exception hierarchy. –

4

Zgodnie z dokumentacją w języku Python jest to zgłaszane, gdy :

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

Wynika to z faktu, że narzędzie głowica odczytuje z stdout, następnie szybko zamyka.

Jak widać, można go obejść, dodając po prostu sys.stdout.flush() po każdym print(). Zauważ, że to czasami nie działa w Pythonie 3.

Można alternatywnie rura go awk jak ten, aby uzyskać taki sam wynik jak head -3:

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

Nadzieja to pomogło, powodzenia!

+0

Dzięki za to. Niestety obejście problemu z awk nie jest opcją. Nie wiem, ile linii generuje rzeczywisty wynik. Pozostawię pytanie bez odpowiedzi przez kilka dni, jeśli nie masz nic przeciwko. Dzięki jeszcze raz. –

+0

@ tommy.carstensen Czy 'sys.stdout.flush()' nie działa? Ponadto, z 'głową' nie musisz także wiedzieć, ile linii będzie i tak? – phantom

+0

@ tommy.carstensen Nie można zrobić tego, o co prosisz. Nie ma sposobu, aby włączyć 'head' do pracy z twoim programem w ten sposób. – phantom

1

Jak widać na wyjściu, który zamieścił ostatni wyjątek jest podniesione w fazie destructor: dlatego trzeba ignored pod koniec

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

Prosty przykład, aby zrozumieć, co się dzieje na tym, że kontekst jest następujący:

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

Każdy wyjątek, który jest wyzwalany, gdy obiekt jest niszczony spowoduje standardowe wyjście błędów, które wyjaśniają wyjątek i ignorowane (to dlatego, że pyton poinformuje cię, że coś nie mogło b poprawnie postępować w fazie niszczenia). W każdym razie tego rodzaju wyjątki nie mogą być buforowane, więc można po prostu usunąć wywołania, które mogą je wygenerować lub zamknąć stderr.

Powróć do pytania.Ten wyjątek nie jest prawdziwym problemem (jak powiedzieć, że jest ignorowany), ale jeśli nie chcesz go wydrukować, musisz zastąpić funkcję, która może zostać wywołana, gdy obiekt zostanie zniszczony lub zamknięty stderr jako poprawnie zasugerowana @SergeBallesta: w tobie przypadku można zamknięciewrite i flush funkcja i żaden wyjątek zostanie uruchomiony w zniszczyć kontekst

który jest przykładem tego, jak można to zrobić:

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

To nie jest rozwiązanie.Musi być w stanie używać go z 'flush = True' z' print' . – phantom

+0

Jak piszę testowałem to na pythonie 3.2 ... teraz dostosowuję go do Pythona 3.4 –

+0

@phantom To jest wersja 'flush = True' w' print' ... To jest rozwiązanie, prawda? –