Sådan håndteres undtagelser i Python: En detaljeret visuel introduktion

Velkommen! I denne artikel lærer du, hvordan du håndterer undtagelser i Python.

Vi vil især dække:

  • Undtagelser
  • Formålet med undtagelseshåndtering
  • Prøveklausulen
  • Den undtagelsesklausul
  • Den anden klausul
  • Den endelige klausul
  • Hvordan man hæver undtagelser

Er du klar? Lad os begynde! ?

1️⃣ Introduktion til undtagelser

Vi starter med undtagelser:

  • Hvad er de?
  • Hvorfor er de relevante?
  • Hvorfor skal du håndtere dem?

Ifølge Python-dokumentationen:

Fejl registreret under udførelse kaldes undtagelser og er ikke ubetinget dødelige.

Undtagelser hæves, når programmet støder på en fejl under dets udførelse. De forstyrrer den normale strøm af programmet og afslutter det normalt brat. For at undgå dette kan du fange dem og håndtere dem korrekt.

Du har sandsynligvis set dem under dine programmeringsprojekter.

Hvis du nogensinde har forsøgt at dele med nul i Python, skal du have set denne fejlmeddelelse:

>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

Hvis du forsøgte at indeksere en streng med et ugyldigt indeks, fik du bestemt denne fejlmeddelelse:

>>> a = "Hello, World" >>> a[456] Traceback (most recent call last): File "", line 1, in  a[456] IndexError: string index out of range

Dette er eksempler på undtagelser.

? Almindelige undtagelser

Der er mange forskellige typer undtagelser, og de er alle rejst i bestemte situationer. Nogle af de undtagelser, som du sandsynligvis vil se, når du arbejder på dine projekter, er:

  • IndexError - hæves, når du prøver at indeksere en liste, tuple eller streng ud over de tilladte grænser. For eksempel:
>>> num = [1, 2, 6, 5] >>> num[56546546] Traceback (most recent call last): File "", line 1, in  num[56546546] IndexError: list index out of range
  • KeyError - hæves, når du prøver at få adgang til værdien af ​​en nøgle, der ikke findes i en ordbog. For eksempel:
>>> students = {"Nora": 15, "Gino": 30} >>> students["Lisa"] Traceback (most recent call last): File "", line 1, in  students["Lisa"] KeyError: 'Lisa'
  • NameError - rejst, når et navn, som du henviser til i koden, ikke findes. For eksempel:
>>> a = b Traceback (most recent call last): File "", line 1, in  a = b NameError: name 'b' is not defined
  • TypeError - hæves, når en operation eller funktion anvendes på et objekt af en upassende type. For eksempel:
>>> (5, 6, 7) * (1, 2, 3) Traceback (most recent call last): File "", line 1, in  (5, 6, 7) * (1, 2, 3) TypeError: can't multiply sequence by non-int of type 'tuple'
  • ZeroDivisionError - hævet, når du prøver at dele med nul.
>>> a = 5/0 Traceback (most recent call last): File "", line 1, in  a = 5/0 ZeroDivisionError: division by zero

? Tip: For at lære mere om andre typer indbyggede undtagelser henvises til denne artikel i Python-dokumentationen.

? En undtagelsens anatomi

Jeg er sikker på, at du skal have bemærket et generelt mønster i disse fejlmeddelelser. Lad os nedbryde deres generelle struktur stykke for stykke:

Først finder vi denne linje (se nedenfor). En traceback er grundlæggende en liste, der beskriver de funktionskald, der blev foretaget, før undtagelsen blev rejst.

Traceback hjælper dig under fejlretningsprocessen, fordi du kan analysere rækkefølgen af ​​funktionsopkald, der resulterede i undtagelsen:

Traceback (most recent call last):

Derefter ser vi denne linje (se nedenfor) med stien til filen og den linje, der rejste undtagelsen. I dette tilfælde var stien Python-skal, da eksemplet blev udført direkte i IDLE.

File "", line 1, in  a - 5/0

Tip: Hvis linjen, der hævede undtagelsen, hører til en funktion, erstattes af funktionens navn.

Endelig ser vi en beskrivende besked, der beskriver undtagelsestypen og giver yderligere oplysninger, der hjælper os med at debugge koden:

NameError: name 'a' is not defined

2️⃣ Undtagelseshåndtering: Formål og sammenhæng

Du kan spørge: hvorfor vil jeg håndtere undtagelser? Hvorfor er dette nyttigt for mig? Ved at håndtere undtagelser kan du give en alternativ udførelsesstrøm for at undgå at nedbrudte dit program uventet.

? Eksempel: Brugerinput

Forestil dig, hvad der ville ske, hvis en bruger, der arbejder med dit program, indtaster et ugyldigt input. Dette ville medføre en undtagelse, fordi der blev udført en ugyldig handling under processen.

Hvis dit program ikke håndterer dette korrekt, går det pludseligt ned, og brugeren får en meget skuffende oplevelse med dit produkt.

Men hvis du håndterer undtagelsen, vil du være i stand til at give et alternativ til at forbedre brugerens oplevelse.

Perhaps you could display a descriptive message asking the user to enter a valid input, or you could provide a default value for the input. Depending on the context, you can choose what to do when this happens, and this is the magic of error handling. It can save the day when unexpected things happen. ⭐️

? What Happens Behind the Scenes?

Basically, when we handle an exception, we are telling the program what to do if the exception is raised. In that case, the "alternative" flow of execution will come to the rescue. If no exceptions are raised, the code will run as expected.

3️⃣ Time to Code: The try ... except Statement

Now that you know what exceptions are and why you should we handle them, we will start diving into the built-in tools that the Python languages offers for this purpose.

First, we have the most basic statement: try ... except.

Let's illustrate this with a simple example. We have this small program that asks the user to enter the name of a student to display his/her age:

students = {"Nora": 15, "Gino": 30} def print_student_age(): name = input("Please enter the name of the student: ") print(students[name]) print_student_age()

Notice how we are not validating user input at the moment, so the user might enter invalid values (names that are not in the dictionary) and the consequences would be catastrophic because the program would crash if a KeyError is raised:

# User Input Please enter the name of the student: "Daniel" # Error Message Traceback (most recent call last): File "", line 15, in  print_student_age() File "", line 13, in print_student_age print(students[name]) KeyError: '"Daniel"'

? Syntax

We can handle this nicely using try ... except. This is the basic syntax:

In our example, we would add the try ... except statement within the function. Let's break this down piece by piece:

students = {"Nora": 15, "Gino": 30} def print_student_age(): while True: try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered") print_student_age()

If we "zoom in", we see the try ... except statement:

try: name = input("Please enter the name of the student: ") print(students[name]) break except: print("This name is not registered")
  • When the function is called, the try clause will run. If no exceptions are raised, the program will run as expected.
  • But if an exception is raised in the try clause, the flow of execution will immediately jump to the except clause to handle the exception.

? Note: This code is contained within a while loop to continue asking for user input if the value is invalid. This is an example:

Please enter the name of the student: "Lulu" This name is not registered Please enter the name of the student: 

This is great, right? Now we can continue asking for user input if the value is invalid.

At the moment, we are handling all possible exceptions with the same except clause. But what if we only want to handle a specific type of exception? Let's see how we could do this.

? Catching Specific Exceptions

Since not all types of exceptions are handled in the same way, we can specify which exceptions we would like to handle with this syntax:

This is an example. We are handling the ZeroDivisionError exception in case the user enters zero as the denominator:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") divide_integers()

Dette ville være resultatet:

# First iteration Please enter the numerator: 5 Please enter the denominator: 0 Please enter a valid denominator. # Second iteration Please enter the numerator: 5 Please enter the denominator: 2 2.5

Vi håndterer dette korrekt. Men ... hvis en anden type undtagelse hæves, vil programmet ikke håndtere det yndefuldt.

Her har vi et eksempel på en ValueError, fordi en af ​​værdierne er en float, ikke en int:

Please enter the numerator: 5 Please enter the denominator: 0.5 Traceback (most recent call last): File "", line 53, in  divide_integers() File "", line 47, in divide_integers b = int(input("Please enter the denominator: ")) ValueError: invalid literal for int() with base 10: '0.5'

Vi kan tilpasse, hvordan vi håndterer forskellige typer undtagelser.

? Flere undtagen klausuler

For at gøre dette skal vi tilføje flere exceptklausuler for at håndtere forskellige typer undtagelser forskelligt.

Ifølge Python-dokumentationen:

En prøveerklæring kan have mere end én undtagen klausul for at specificere håndterere for forskellige undtagelser. Der udføres højst en handler .

I dette eksempel har vi to undtagen klausuler. Den ene håndterer ZeroDivisionError og den anden håndterer ValueError, de to typer undtagelser, der kunne hæves i denne prøveblok.

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except ZeroDivisionError: print("Please enter a valid denominator.") except ValueError: print("Both values have to be integers.") divide_integers() 

? Tip: Du er nødt til at bestemme, hvilke typer af undtagelser kan hæves i try-blok til at håndtere dem korrekt.

? Flere undtagelser, én undtagelse

Du kan også vælge at håndtere forskellige typer undtagelser med den samme undtagelsesbestemmelse.

Ifølge Python-dokumentationen:

En undtagelsesklausul kan nævne flere undtagelser som en parentes tuple.

Dette er et eksempel, hvor vi fanger to undtagelser (ZeroDivisionError og ValueError) med samme exceptklausul:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) except (ZeroDivisionError, ValueError): print("Please enter valid integers.") divide_integers()

Outputtet ville være det samme for de to typer undtagelser, fordi de håndteres af den samme undtagen klausul:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers.
Please enter the numerator: 0.5 Please enter valid integers. Please enter the numerator: 

? Håndtering af undtagelser, der er rejst med funktioner kaldet i prøveklausulen

Et interessant aspekt ved undtagelseshåndtering er, at hvis en undtagelse hæves i en funktion, der tidligere blev kaldt i prøveklausulen for en anden funktion, og selve funktionen ikke håndterer den, håndterer den, der kalder op, hvis der er en passende undtagen klausul.

Ifølge Python-dokumentationen:

Undtagelsesbehandlere håndterer ikke kun undtagelser, hvis de optræder straks i prøveklausulen, men også hvis de forekommer i funktioner, der kaldes (selv indirekte) i prøveklausulen.

Lad os se et eksempel for at illustrere dette:

def f(i): try: g(i) except IndexError: print("Please enter a valid index") def g(i): a = "Hello" return a[i] f(50)

We have the f function and the g function. f calls g in the try clause. With the argument 50, g will raise an IndexError because the index 50 is not valid for the string a.

But g itself doesn't handle the exception. Notice how there is no try ... except statement in the g function. Since it doesn't handle the exception, it "sends" it to f to see if it can handle it, as you can see in the diagram below:

Since f does know how to handle an IndexError, the situation is handled gracefully and this is the output:

Please enter a valid index

? Note: If f had not handled the exception, the program would have ended abruptly with the default error message for an IndexError.

? Adgang til specifikke detaljer om undtagelser

Undtagelser er objekter i Python, så du kan tildele undtagelsen, der blev hævet til en variabel. På denne måde kan du udskrive standardbeskrivelsen af ​​undtagelsen og få adgang til dens argumenter.

Ifølge Python-dokumentationen:

Undtagelsesklausulen kan specificere en variabel efter undtagelsesnavnet . Variablen er bundet til en undtagelsesforekomst med de argumenter, der er gemt i instans.

Her har vi et eksempel (se nedenfor), hvor vi tildelte forekomsten ZeroDivisionErrortil variablen e. Derefter kan vi bruge denne variabel inden for undtagelsesklausulen til at få adgang til typen af ​​undtagelsen, dens meddelelse og argumenter.

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) print(a / b) # Here we assign the exception to the variable e except ZeroDivisionError as e: print(type(e)) print(e) print(e.args) divide_integers()

Den tilsvarende output ville være:

Please enter the numerator: 5 Please enter the denominator: 0 # Type  # Message division by zero # Args ('division by zero',)

? Tip: Hvis du er fortrolig med specielle metoder, ifølge Python-dokumentationen: "for nemheds skyld definerer undtagelsesforekomsten, __str__()så argumenterne kan udskrives direkte uden at skulle referere .args."

4️⃣ Lad os nu tilføje: Den "anden" klausul

Den elseklausul er valgfrit, men det er et fantastisk værktøj, fordi det lader os udføre kode, der kun køre, hvis ingen undtagelser er blevet rejst under klausulen prøve.

Ifølge Python-dokumentationen:

Den try... exceptudsagn har en valgfri andet klausul , som, når de findes, skal følge alle undtagen klausuler. Det er nyttigt for kode, der skal udføres, hvis prøveklausulen ikke giver en undtagelse.

Her er et eksempel på brugen af elseklausulen:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) divide_integers()

Hvis der ikke hæves nogen undtagelse, udskrives resultatet:

Please enter the numerator: 5 Please enter the denominator: 5 1.0

Men hvis en undtagelse hæves, udskrives resultatet ikke:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero

? Tip: I henhold til Python-dokumentationen:

Brugen af elseklausulen er bedre end at tilføje yderligere kode til tryklausulen, fordi den undgår ved en fejltagelse at fange en undtagelse, der ikke blev rejst af koden, der blev beskyttet af try... exceptudsagnet.

5️⃣ "Endelig" klausul

Den endelige klausul er den sidste klausul i denne sekvens. Det er valgfrit , men hvis du inkluderer det, skal det være den sidste klausul i sekvensen. Den finallyklausul er altid henrettet, selv om en undtagelse blev rejst i klausulen prøve.  

Ifølge Python-dokumentationen:

Hvis en finallyklausul er til stede, finallyudføres klausulen som den sidste opgave, før tryerklæringen er afsluttet. Den finallyklausul kører hvorvidt tryudsagn producerer en undtagelse.

Den endelige klausul bruges normalt til at udføre "oprydnings" -handlinger, der altid skal gennemføres. For eksempel, hvis vi arbejder med en fil i prøveklausulen, skal vi altid lukke filen, selvom der blev rejst en undtagelse, da vi arbejdede med dataene.

Her er et eksempel på den endelige klausul:

def divide_integers(): while True: try: a = int(input("Please enter the numerator: ")) b = int(input("Please enter the denominator: ")) result = a / b except (ZeroDivisionError, ValueError): print("Please enter valid integers. The denominator can't be zero") else: print(result) finally: print("Inside the finally clause") divide_integers()

Dette er output, når der ikke blev rejst nogen undtagelser:

Please enter the numerator: 5 Please enter the denominator: 5 1.0 Inside the finally clause

Dette er output, når en undtagelse blev rejst:

Please enter the numerator: 5 Please enter the denominator: 0 Please enter valid integers. The denominator can't be zero Inside the finally clause

Bemærk hvordan finallyklausulen altid kører.

❗️Important: remember that the else clause and the finally clause are optional, but if you decide to include both, the finally clause has to be the last clause in the sequence.

6️⃣ Raising Exceptions

Now that you know how to handle exceptions in Python, I would like to share with you this helpful tip: you can also choose when to raise exceptions in your code.

This can be helpful for certain scenarios. Let's see how you can do this:

This line will raise a ValueError with a custom message.

Here we have an example (see below) of a function that prints the value of the items of a list or tuple, or the characters in a string. But you decided that you want the list, tuple, or string to be of length 5. You start the function with an if statement that checks if the length of the argument data is 5. If it isn't, a ValueError exception is raised:

def print_five_items(data): if len(data) != 5: raise ValueError("The argument must have five elements") for item in data: print(item) print_five_items([5, 2])

The output would be:

Traceback (most recent call last): File "", line 122, in  print_five_items([5, 2]) File "", line 117, in print_five_items raise ValueError("The argument must have five elements") ValueError: The argument must have five elements

Notice how the last line displays the descriptive message:

ValueError: The argument must have five elements

You can then choose how to handle the exception with a try ... except statement. You could add an else clause and/or a finally clause. You can customize it to fit your needs.

? Helpful Resources

  • Exceptions
  • Handling Exceptions
  • Defining Clean-up Actions

Jeg håber du nød at læse min artikel og fandt det nyttigt. Nu har du de nødvendige værktøjer til at håndtere undtagelser i Python, og du kan bruge dem til din fordel, når du skriver Python-kode. ? Tjek mine online kurser. Du kan følge mig på Twitter.

⭐️ Du kan muligvis nyde mine andre gratisCodeCamp / nyhedsartikler:

  • @Property Decorator i Python: Dens brugstilfælde, fordele og syntaks
  • Datastrukturer 101: Grafer - En visuel introduktion til begyndere
  • Datastrukturer 101: Arrays - En visuel introduktion til begyndere