2011-01-05 9 views
32

Mam fragment kodu w Pythonie, który wydaje się powodować błąd w sposób probabilistyczny, ponieważ uzyskuje dostęp do serwera i czasami ten serwer ma błąd 500 wewnętrzny serwer. Chcę próbować, dopóki nie otrzymam błędu. Moje rozwiązanie to:W Pythonie spróbuj aż do braku błędu

while True: 
    try: 
     #code with possible error 
    except: 
     continue 
    else: 
     #the rest of the code 
     break 

To wydaje mi się hackem do mnie. Czy jest to bardziej Pythoniczny sposób na zrobienie tego?

+4

Błąd ... co się stanie, gdy zdalny serwer zginie? Czy to się stanie, zużywając 100% rdzenia procesora? – user9876

+0

continue powinno być w innym miejscu i włamać się z wyjątkiem. Czy to literówka? –

+2

@aand: Nie. Jeśli wystąpi wyjątek, chce spróbować jeszcze raz (czytaj: 'kontynuuj'), ale jeśli nie ma wyjątku, chce coś (naszkicować komentarzem) i wydostać się z tego dziwnego nadużycia pętla. ("else" jest wykonywany, jeśli nie ma wyjątku, czy brakujący element?) – delnan

Odpowiedz

33

Nie dostanie znacznie czystsze. To nie jest zbyt proste zadanie. W najlepszym przypadku (co i tak byłoby bardziej czytelne, ponieważ warunek dla break jest na górze z while), można utworzyć zmienną result = None i zapętlić, gdy będzie to is None. Powinieneś także dostosować zmienne i możesz zastąpić continue semantycznie być może poprawnym pass (nie przejmujesz się, jeśli wystąpi błąd, po prostu chcesz go zignorować) i upuścić break - to również dostanie resztę kodu, który wykonuje tylko jeden raz, poza pętlą. Zauważ, że klauzule except: są złe z powodów given in the documentation.

Przykład zawierające wszystkie powyższe:

result = None 
while result is None: 
    try: 
     # connect 
     result = get_data(...) 
    except: 
     pass 
# other code that uses result but is not involved in getting it 
+3

Jeśli istnieje stały powód, że połączenie się nie udaje, to rozwiązanie będzie rozdrabniać w nieskończonej pętli. –

+3

@BradKoch Oczywiście. Jest to nieodłączne od pytania, a ponadto wszelkie poprawki (takie jak ogólny limit czasu lub ograniczona liczba prób) są względnie prostopadłe do zmian, które opisuję. – delnan

+2

Ale każda proponowana odpowiedź powinna być bezpieczna lub przynajmniej pamiętać o pułapkach. To nie zapewnia ochrony przed 100% zużyciem procesora i zagraża przyszłym czytelnikom. –

15

Może coś takiego:

connected = False 

while not connected: 
    try: 
     try_connect() 
     connected = True 
    except ...: 
     pass 
+3

Każda proponowana odpowiedź powinna być bezpieczna lub przynajmniej nie zauważaj pułapek. To nie zapewnia ochrony przed 100% zużyciem procesora i zagraża przyszłym czytelnikom. –

1

Może podstawie dekorator? Możesz przekazać listę argumentów jako wyjątki, na których chcemy się ponownie spróbować i/lub liczbę prób.

def retry(exceptions=None, tries=None): 
    if exceptions: 
     exceptions = tuple(exceptions) 
    def wrapper(fun): 
     def retry_calls(*args, **kwargs): 
      if tries: 
       for _ in xrange(tries): 
        try: 
         fun(*args, **kwargs) 
        except exceptions: 
         pass 
        else: 
         break 
      else: 
       while True: 
        try: 
         fun(*args, **kwargs) 
        except exceptions: 
         pass 
        else: 
         break 
     return retry_calls 
    return wrapper 


from random import randint 

@retry([NameError, ValueError]) 
def foo(): 
    if randint(0, 1): 
     raise NameError('FAIL!') 
    print 'Success' 

@retry([ValueError], 2) 
def bar(): 
    if randint(0, 1): 
     raise ValueError('FAIL!') 
    print 'Success' 

@retry([ValueError], 2) 
def baz(): 
    while True: 
     raise ValueError('FAIL!') 

foo() 
bar() 
baz() 

oczywiście część „spróbować” powinien być przeniesiony do innego funcion poniewaz używamy go w obu pętlach, ale to tylko przykład;)

+0

Trochę spóźnionego komentarza, ale podwojenie kodu można było uniknąć, używając "dla _ w itertools.repeat (None, times = tries):" Jeśli try jest None, pętla trwa na zawsze, ale jeśli próbuje to liczba kończy się po wielu iteracjach. –

0

Oto krótki fragment kodu używam do przechwytywania błędu jako ciąg. Spróbuję ponownie, aż się uda. To wychwytuje wszystkie wyjątki, ale możesz to zmienić, jak chcesz.

start = 0 
str_error = "Not executed yet." 
while str_error: 
    try: 
     # replace line below with your logic , i.e. time out, max attempts 
     start = raw_input("enter a number, 0 for fail, last was {0}: ".format(start)) 
     new_val = 5/int(start) 
     str_error=None 
    except Exception as str_error: 
     pass 

UWAGA: Kod ten zostanie zatrzymany w wieki pętli aż wystąpi żaden wyjątek. To tylko prosty przykład, a MIGHT wymaga wcześniejszego wyjścia z pętli lub spania pomiędzy kolejnymi próbami.

6

To jest jeden, który mocno zawiedzie po 4 próbach i oczekuje 2 sekund między próbami. Zmienić, jak chcesz, aby dostać to, czego chcesz tworzyć ten:

from time import sleep 

for x in range(0, 4): # try 4 times 
    try: 
     # msg.send() 
     # put your logic here 
     str_error = None 
    except Exception as str_error: 
     pass 

    if str_error: 
     sleep(2) # wait for 2 seconds before trying to fetch the data again 
    else: 
     break 

Oto przykład z backoff:

from time import sleep 

sleep_time = 2 
num_retries = 4 
for x in range(0, num_retries): 
    try: 
     # put your logic here 
     str_error = None 
    except Exception as str_error: 
     pass 

    if str_error: 
     sleep(sleep_time) # wait before trying to fetch the data again 
     sleep_time *= 2 # Implement your backoff algorithm here i.e. exponential backoff 
    else: 
     break 
+1

Ta odpowiedź jest lepsza od innych, ponieważ ta jest "przyjemna" dla innych procesów ze względu na funkcję spania, a także ma ograniczone próby. –

1

Oto funkcja narzędzie, które napisałem do zawijania powtórzenie aż do sukcesu w neater pakiet. Używa tej samej podstawowej struktury, ale zapobiega powtórzeniu. Można go zmodyfikować, aby stosunkowo łatwo złapać i ponownie rzucić wyjątek na ostatnią próbę.

def try_until(func, max_tries, sleep_time): 
    for _ in range(0,max_tries): 
     try: 
      return func() 
     except: 
      sleep(sleep_time) 
    raise WellNamedException() 
    #could be 'return sensibleDefaultValue' 

można wówczas nazywa się ten

result = try_until(my_function, 100, 1000) 

Jeśli trzeba przekazywać argumenty do my_function, można też to zrobić poprzez try_until argumenty, albo przez owinięcie go w lambda żaden argument :

result = try_until(lambda : my_function(x,y,z), 100, 1000) 
1

The itertools.iter_except recepty oddaje tę ideę „nazywając wielokrotnie funkcję dopóki jest wyjątek”. Jest podobny do zaakceptowanej odpowiedzi, ale zamiast tego przepis podaje iterator.

Od Przepisy:

def iter_except(func, exception, first=None): 
    """ Call a function repeatedly until an exception is raised.""" 
    try: 
     if first is not None: 
      yield first()   # For database APIs needing an initial cast to db.first() 
     while True: 
      yield func() 
    except exception: 
     pass 

Można oczywiście realizować ten drugi kod bezpośrednio. Dla wygody używam oddzielnej biblioteki, more_itertools, która implementuje ten przepis dla nas (opcjonalnie).

przykład:

import more_itertools as mit 

list(mit.iter_except([0, 1, 2].pop, IndexError)) 
# [2, 1, 0] 

Tutaj pop Sposób (a biorąc pod uwagę funkcji) jest wywoływana dla każdej iteracji przedmiotu do czasu aż w IndexError jest podniesiony.

Twoim przypadku, biorąc pod uwagę niektóre connect_function i oczekiwany błąd, można dokonać iterator, który wywołuje wielokrotnie funkcję aż wyjątek jest podniesiona, np

mit.iter_except(connect_function, ConnectionError) 

W tym momencie, traktować go jak każdy inny iterator przez zapętlenie go lub wywołanie next().

Powiązane problemy