Articles

Obsługa wyjątków i błędów w Pythonie

Wprowadzenie


Źródło

Zanim przejdziemy do tego, dlaczego obsługa wyjątków jest niezbędna i jakie typy wbudowanych wyjątków obsługuje Python.w wyjątki, które obsługuje Python, należy zrozumieć, że istnieje subtelna różnica między error a exception.

Błędy nie mogą być obsługiwane, podczas gdy wyjątki Pythona mogą być obsługiwane w czasie wykonywania. Błąd może być syntax (błąd parsowania), podczas gdy może istnieć wiele typów wyjątków, które mogą wystąpić w czasie wykonywania i nie są bezwarunkowo niedziałające. Error może wskazywać na krytyczne problemy, których rozsądna aplikacja nie powinna próbować złapać, podczas gdy Exception może wskazywać warunki, które aplikacja powinna próbować złapać. Błędy są formą niezaznaczonego wyjątku i są nieodwracalne jak OutOfMemoryError, których programista nie powinien próbować obsługiwać.

Obsługa wyjątków czyni twój kod bardziej solidnym i pomaga zapobiegać potencjalnym awariom, które spowodowałyby niekontrolowane zatrzymanie programu. Wyobraź sobie, że napisałeś kod, który został wdrożony do produkcji, a mimo to, kończy się z powodu wyjątku, twój klient nie doceniłby tego, więc lepiej jest obsłużyć dany wyjątek wcześniej i uniknąć chaosu.

Błędy mogą być różnych typów:

  • Błąd składni
  • Błąd braku pamięci
  • Błąd rekursji
  • Wyjątki

Sprawdźmy je jeden po drugim.

Błąd składni

Błędy składni, często nazywane błędami parsowania, są powodowane głównie wtedy, gdy parser wykryje problem składniowy w twoim kodzie.

Bierzmy przykład, aby to zrozumieć.

a = 8b = 10c = a b
 File "<ipython-input-8-3b3ffcedf995>", line 3 c = a b ^SyntaxError: invalid syntax

Powyższa strzałka wskazuje, kiedy parser napotkał błąd podczas wykonywania kodu. Token poprzedzający strzałkę powoduje awarię. Aby naprawić takie podstawowe błędy, Python wykona większość twojej pracy, ponieważ wydrukuje dla ciebie nazwę pliku i numer linii, w której wystąpił błąd.

Błąd braku pamięci

Błędy pamięci zależą głównie od pamięci RAM twojego systemu i są związane z Heap. Jeśli masz duże obiekty (lub) obiekty referencyjne w pamięci, wtedy zobaczysz OutofMemoryError (Source). Może to być spowodowane z różnych powodów:

  • Używanie 32-bitowej architektury Pythona (maksymalna alokacja pamięci jest bardzo niska, pomiędzy 2GB – 4GB).
  • Wczytywanie bardzo dużego pliku danych
  • Uruchamianie modelu Machine Learning/Deep Learning i wiele innych.

Z błędem memory można sobie poradzić za pomocą obsługi wyjątków, wyjątku awaryjnego dla sytuacji, gdy interpreterowi całkowicie zabraknie pamięci i musi natychmiast przerwać bieżące wykonywanie. W tych rzadkich przypadkach Python podnosi OutofMemoryError, pozwalając skryptowi w jakiś sposób catch się i break wyjść z błędu pamięci i odzyskać siebie.

Jednakże, ponieważ Python przyjmuje architekturę zarządzania pamięcią języka C (funkcja malloc()), nie ma pewności, że wszystkie procesy skryptu odzyskają pamięć – w niektórych przypadkach błąd MemoryError spowoduje nieodwracalną awarię. Stąd też ani nie jest dobrą praktyką stosowanie obsługi wyjątków dla takiego błędu, ani nie jest to wskazane.

Błąd rekursji

Jest on związany z stack i występuje w momencie wywołania funkcji. Jak sama nazwa wskazuje, recursion błąd pojawia się, gdy wykonywanych jest zbyt wiele metod, jedna wewnątrz drugiej (jedna z nieskończoną rekurencją), która jest ograniczona przez rozmiar stosu.

Wszystkie twoje zmienne lokalne i dane związane z wywołaniem metod zostaną umieszczone na stosie. Dla każdego wywołania metody zostanie utworzona jedna ramka stosu, a dane lokalne oraz dane związane z wywołaniem metody zostaną umieszczone wewnątrz tej ramki stosu. Po zakończeniu wykonywania metody ramka stosu zostanie usunięta.

Aby odtworzyć ten błąd, zdefiniujmy funkcję recursion która będzie rekurencyjna, co oznacza, że będzie ciągle wywoływać siebie jako wywołanie metody w nieskończonej pętli, zobaczysz StackOverflow lub Recursion Error, ponieważ ramka stosu zostanie wypełniona danymi metody dla każdego wywołania, ale nie zostanie zwolniona.

def recursion(): return recursion()
recursion()
---------------------------------------------------------------------------RecursionError Traceback (most recent call last)<ipython-input-3-c6e0f7eb0cde> in <module>----> 1 recursion()<ipython-input-2-5395140f7f05> in recursion() 1 def recursion():----> 2 return recursion()... last 1 frames repeated, from the frame below ...<ipython-input-2-5395140f7f05> in recursion() 1 def recursion():----> 2 return recursion()RecursionError: maximum recursion depth exceeded

Błąd wcięcia

Błąd wcięcia jest podobny w duchu do błędu składni i podpada pod niego. Jednak jest specyficzny dla jedynych problemów związanych z wcięciem w skrypcie.

Przyjrzyjmy się więc szybkiemu przykładowi, aby zrozumieć błąd wcięcia.

for i in range(10):print('Hello world')
 File "<ipython-input-6-628f419d2da8>", line 2 print('Hello world') ^IndentationError: expected an indented block

Wyjątki

Nawet jeśli składnia instrukcji lub wyrażenia jest poprawna, nadal może spowodować błąd podczas wykonywania. Wyjątki w Pythonie to błędy, które są wykrywane podczas wykonywania instrukcji i nie są bezwarunkowo śmiertelne: wkrótce dowiesz się w tym poradniku, jak radzić sobie z nimi w programach Pythona. Obiekt wyjątku jest tworzony, gdy skrypt Pythona zgłasza wyjątek. Jeśli skrypt jawnie nie obsłuży tego wyjątku, program zostanie zmuszony do nagłego zakończenia działania.

Programy zwykle nie obsługują wyjątków, a ich wynikiem są komunikaty o błędach, jak pokazano tutaj:

Błąd typu

a = 2b = 'DataCamp'a + b
---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-7-86a706a0ffdf> in <module> 1 a = 2 2 b = 'DataCamp'----> 3 a + bTypeError: unsupported operand type(s) for +: 'int' and 'str'

Błąd dzielenia przez zero

100 / 0
---------------------------------------------------------------------------ZeroDivisionError Traceback (most recent call last)<ipython-input-43-e9e866a10e2a> in <module>----> 1 100 / 0ZeroDivisionError: division by zero

W Pythonie występują różne typy wyjątków, a ich typ jest drukowany jako część komunikatu: typy w powyższych dwóch przykładach to ZeroDivisionError i TypeError. W obu przypadkach typ wyjątku jest nazwą wbudowanego wyjątku Pythona.

Pozostała część wiersza błędu zawiera szczegóły dotyczące tego, co spowodowało błąd, w oparciu o typ wyjątku.

Przyjrzyjrzyjmy się teraz wbudowanym wyjątkom Pythona.

Wbudowane wyjątki


Źródło

Zanim zaczniesz poznawać wbudowane wyjątki, szybko przejrzyjmy cztery główne komponenty obsługi wyjątków, jak pokazano na tym rysunku.

  • Próbuje: Spowoduje uruchomienie bloku kodu, w którym spodziewasz się wystąpienia błędu.
  • Except: Tutaj określisz typ wyjątku, którego oczekujesz w bloku try (wbudowany lub niestandardowy).
  • Else: Jeśli nie ma żadnego wyjątku, to ten blok kodu zostanie wykonany (potraktuj to jako środek zaradczy lub opcję awaryjną, jeśli spodziewasz się, że jakaś część twojego skryptu wygeneruje wyjątek).
  • Na koniec: Niezależnie od tego, czy wystąpi wyjątek, czy nie, ten blok kodu zawsze zostanie wykonany.

W dalszej części tutoriala poznasz typowe rodzaje wyjątków, a także nauczysz się je obsługiwać za pomocą obsługi wyjątków.

Błąd przerwania klawiatury

Wyjątek KeyboardInterrupt jest podnoszony, gdy próbujemy zatrzymać działający program przez naciśnięcie ctrl+c lub ctrl+z w wierszu poleceń lub przerwanie jądra w Jupyter Notebook. Czasami możesz nie zamierzać przerwać programu, ale przez pomyłkę tak się dzieje, w którym to przypadku użycie obsługi wyjątków w celu uniknięcia takich problemów może być pomocne.

W poniższym przykładzie, jeśli uruchomisz komórkę i przerwiesz jądro, program podniesie wyjątek KeyboardInterrupt.inp = input()Zajmijmy się teraz obsługą wyjątku KeyboardInterrupt.

try: inp = input() print ('Press Ctrl+C or Interrupt the Kernel:')except KeyboardInterrupt: print ('Caught KeyboardInterrupt')else: print ('No exception occurred')

Caught KeyboardInterrupt

Standardowy błąd

Poznajmy kilka standardowych błędów, które zazwyczaj mogą wystąpić podczas programowania.

Błąd arytmetyczny

  • Zero Division Error
  • OverFlow Error
  • Floating Point Error

Wszystkie powyższe wyjątki należą do klasy bazowej Arithmetic i są podnoszone w przypadku błędów w operacjach arytmetycznych, tak jak to omówiono tutaj.

Podział przez zero

Gdy dzielnik (drugi argument dzielenia) lub mianownik jest równy zero, to wynik podnosi błąd podziału przez zero.

try: a = 100 / 0 print (a)except ZeroDivisionError: print ("Zero Division Exception Raised." )else: print ("Success, no error!")
Zero Division Exception Raised.

OverFlow Error

Błąd Overflow jest podnoszony, gdy wynik operacji arytmetycznej jest poza zakresem. OverflowError jest podnoszony dla liczb całkowitych, które są poza wymaganym zakresem.

try: import math print(math.exp(1000))except OverflowError: print ("OverFlow Exception Raised.")else: print ("Success, no error!")
OverFlow Exception Raised.

Błąd asercji

Gdy instrukcja asercji nie powiedzie się, podnoszony jest Błąd asercji.

Przyjmijmy przykład, aby zrozumieć błąd asercji. Załóżmy, że masz dwie zmienne a i b, które musisz porównać. Aby sprawdzić, czy a i b są równe, czy nie, stosujesz przed tym słowo kluczowe assert, które podniesie wyjątek Assertion, gdy wyrażenie zwróci wartość false.

try: a = 100 b = "DataCamp" assert a == bexcept AssertionError: print ("Assertion Exception Raised.")else: print ("Success, no error!")
Assertion Exception Raised.

Błąd atrybutu

Gdy odwołujemy się do nieistniejącego atrybutu i gdy to odwołanie lub przypisanie atrybutu nie powiedzie się, podnoszony jest błąd atrybutu.

W poniższym przykładzie można zauważyć, że obiekt klasy Attributes nie posiada atrybutu o nazwie attribute.

class Attributes(object): a = 2 print (a)try: object = Attributes() print (object.attribute)except AttributeError: print ("Attribute Exception Raised.")
2Attribute Exception Raised.

Błąd importu

ImportError jest podnoszony, gdy próbujemy zaimportować moduł, który nie istnieje (nie można go załadować) w jego standardowej ścieżce lub nawet gdy zrobimy literówkę w nazwie modułu.

import nibabel
---------------------------------------------------------------------------ModuleNotFoundError Traceback (most recent call last)<ipython-input-6-9e567e3ae964> in <module>----> 1 import nibabelModuleNotFoundError: No module named 'nibabel'

Lookup Error

Lookup Error działa jako klasa bazowa dla wyjątków, które występują, gdy klucz lub indeks używany na mapowaniu lub sekwencji listy/słownika jest nieprawidłowy lub nie istnieje.

Dwa typy wyjątków są następujące:

  • IndexError
  • KeyError

Key Error

Jeśli klucz, do którego próbujemy uzyskać dostęp, nie został znaleziony w słowniku, podnoszony jest wyjątek key error.

try: a = {1:'a', 2:'b', 3:'c'} print (a) except LookupError: print ("Key Error Exception Raised.")else: print ("Success, no error!")
Key Error Exception Raised.

Błąd indeksu

Gdy próbujesz uzyskać dostęp do indeksu (sekwencji) listy, która nie istnieje na tej liście lub jest poza zakresem tej listy, podnoszony jest błąd indeksu.

try: a = print (a) except LookupError: print ("Index Error Exception Raised, list index out of range")else: print ("Success, no error!")
Index Error Exception Raised, list index out of range

Błąd pamięci

Jak omówiono wcześniej, błąd pamięci jest podnoszony, gdy operacja nie otrzymuje wystarczającej ilości pamięci do dalszego przetwarzania.

Name Error

Name Error jest podnoszony, gdy lokalna lub globalna nazwa nie jest znaleziona.

W poniższym przykładzie, ans zmienna nie jest zdefiniowana. W związku z tym, otrzymamy name error.

try: print (ans)except NameError: print ("NameError: name 'ans' is not defined")else: print ("Success, no error!")
NameError: name 'ans' is not defined

Błąd czasu pracy

Błąd niezaimplementowany

Ta część poradnika pochodzi z tego Źródła. Runtime Error działa jako klasa bazowa dla NotImplemented Error. Metody abstrakcyjne w klasach zdefiniowanych przez użytkownika powinny podnosić ten wyjątek, gdy klasy pochodne nadpisują metodę.

class BaseClass(object): """Defines the interface""" def __init__(self): super(BaseClass, self).__init__() def do_something(self): """The interface, not implemented""" raise NotImplementedError(self.__class__.__name__ + '.do_something')class SubClass(BaseClass): """Implementes the interface""" def do_something(self): """really does something""" print (self.__class__.__name__ + ' doing something!')SubClass().do_something()BaseClass().do_something()
SubClass doing something!---------------------------------------------------------------------------NotImplementedError Traceback (most recent call last)<ipython-input-1-57792b6bc7e4> in <module> 14 15 SubClass().do_something()---> 16 BaseClass().do_something()<ipython-input-1-57792b6bc7e4> in do_something(self) 5 def do_something(self): 6 """The interface, not implemented"""----> 7 raise NotImplementedError(self.__class__.__name__ + '.do_something') 8 9 class SubClass(BaseClass):NotImplementedError: BaseClass.do_something

Błąd typu

Wyjątek błędu typu jest podnoszony, gdy dwa różne lub niepowiązane typy operandów lub obiektów są połączone.

W poniższym przykładzie, liczba całkowita i ciąg znaków są dodawane, co powoduje błąd type.

try: a = 5 b = "DataCamp" c = a + bexcept TypeError: print ('TypeError Exception Raised')else: print ('Success, no error!')
TypeError Exception Raised

Błąd wartości

Błąd wartości jest podnoszony, gdy wbudowana operacja lub funkcja otrzymuje argument, który ma poprawny type, ale nieprawidłowy value.

W poniższym przykładzie, wbudowana operacja float otrzymuje argument, który jest ciągiem znaków (wartość), który jest nieprawidłowy dla typu float.

try: print (float('DataCamp'))except ValueError: print ('ValueError: could not convert string to float: \'DataCamp\'')else: print ('Success, no error!')
ValueError: could not convert string to float: 'DataCamp'

Wyjątki niestandardowe Pythona

Ta część poradnika wywodzi się z tego Źródła.

Jak wynika z poprzedniej części poradnika, Python posiada wiele wbudowanych wyjątków, które można wykorzystać w swoim programie. Mimo to, czasami możesz potrzebować stworzyć niestandardowe wyjątki z niestandardowymi komunikatami, aby służyły Twojemu celowi.

Możesz to osiągnąć, tworząc nową klasę, która będzie pochodzić z predefiniowanej klasy Exception w Pythonie.

class UnAcceptedValueError(Exception): def __init__(self, data): self.data = data def __str__(self): return repr(self.data)Total_Marks = int(input("Enter Total Marks Scored: "))try: Num_of_Sections = int(input("Enter Num of Sections: ")) if(Num_of_Sections < 1): raise UnAcceptedValueError("Number of Sections can't be less than 1")except UnAcceptedValueError as e: print ("Received error:", e.data)
Enter Total Marks Scored: 10Enter Num of Sections: 0Received error: Number of Sections can't be less than 1

W powyższym przykładzie, jak zauważyłeś, jeśli wpiszesz cokolwiek mniejszego niż 1, niestandardowy wyjątek zostanie podniesiony i obsłużony. Wiele standardowych modułów definiuje swoje wyjątki, aby zgłaszać błędy, które mogą wystąpić w definiowanych przez nie funkcjach.

Demerytury obsługi wyjątków w Pythonie

Używanie obsługi wyjątków w Pythonie ma również skutki uboczne. Podobnie jak programy, które wykorzystują bloki try-except do obsługi wyjątków, będą działać nieco wolniej, a rozmiar Twojego kodu wzrośnie.

Poniżej znajduje się przykład, w którym moduł timeit w Pythonie jest używany do sprawdzania czasu wykonania 2 różnych instrukcji. W stmt1, try-except jest używany do obsługi ZeroDivisionError, podczas gdy w stmt2if oświadczenie jest używane jako normalny warunek sprawdzania. Następnie wykonujesz te stwierdzenia 10000 razy ze zmienną a=0. Punktem do zauważenia tutaj jest to, że czas wykonania obu stwierdzeń jest inny. Zauważysz, że stmt1, który obsługuje wyjątek, zajął nieco więcej czasu niż stmt2, który po prostu sprawdza wartość i nie robi nic, jeśli warunek nie jest spełniony.

Więc, powinieneś ograniczyć użycie obsługi wyjątków w Pythonie i używać jej tylko w rzadkich przypadkach. Na przykład, gdy nie jesteś pewien, czy dane wejściowe będą liczbami całkowitymi czy zmiennoprzecinkowymi dla obliczeń arytmetycznych lub nie jesteś pewien istnienia pliku podczas próby jego otwarcia.

import timeitsetup="a=0"stmt1 = '''\try: b=10/aexcept ZeroDivisionError: pass'''
stmt2 = '''\if a!=0: b=10/a'''
print("time=",timeit.timeit(stmt1,setup,number=10000))print("time=",timeit.timeit(stmt2,setup,number=10000))
time= 0.003897680000136461time= 0.0002797570000439009

Gratulujemy ukończenia tego poradnika.

Jak się dowiedziałeś, wyjątkowa obsługa pomaga przełamać typowy przepływ sterowania w twoim programie, dostarczając mechanizm do oddzielenia obsługi błędów Pythona i sprawia, że twój kod jest bardziej wytrzymały.

Pythonowa wyjątkowa obsługa jest jednym z głównych czynników, które sprawiają, że twój kod jest gotowy do produkcji i przyszłościowy, oprócz dodawania testów jednostkowych i programowania obiektowego.

Jest to potężna technika i jest koncepcją składającą się z zaledwie czterech bloków. try blok szuka wyjątków rzucanych przez kod, podczas gdy except blok obsługuje te wyjątki (wbudowane i niestandardowe).

Jednym z dobrych ćwiczeń dla was wszystkich byłoby użycie wszystkich czterech komponentów wyjątkowej obsługi i spróbowanie uczynić swój kod bardziej solidnym.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *