Python Exceptions (Ausnahmebehandlung)
What Are Exceptions in Python?
When an unexpected condition is encountered while running a Python code, the program stops its execution and throws an error. There are basically two types of errors in Python: syntax errors and exceptions. To understand the difference between these two types, let’s run the following piece of code:
print(x print(1)
A syntax error was thrown since we forgot to close the parenthesis.
This type of error is always raised when we use a statement that is
syntactically incorrect in Python. The parser shows the place where the
syntax error was detected by a little arrow ^
.
Notice also that the subsequent line print(1)
was not
executed since the Python interpreter stopped working when the error occurred.
Let’s fix this error and re-run the code:
print(x) print(1)
Now when we have fixed the wrong syntax, we got another type of error: an exception.
In other words, an exception is a type of error that occurs when a syntactically
correct Python code raises an error.
An arrow indicates the line where the exception occurred, while the
last line of the error message specifies the exact type of exception and
provides its description to facilitate debugging. In our case, it is a NameError
since we tried to print the value of a variable x
that was not defined before. Also in this case, the second line of our piece of code print(1)
was not executed because the normal flow of the Python program was interrupted.
To prevent a sudden program crash, it is important to catch and handle exceptions. For example, provide an alternative version of the code execution when the given exception occurs. This is what we are going to learn next.
Standard Built-in Types of Exceptions
Python provides many types of exceptions thrown in various situations. Let’s take a look at the most common built-in exceptions with their examples:
NameError
– Raised when a name doesn’t exist among either local or global variables:
print(x)
TypeError
– Raised when an operation is run on an inapplicable data type:
print(1+'1')
ValueError
– Raised when an operation or function takes in an invalid value of an argument:
print(int('a'))
IndexError
– Raised when an index doesn’t exist in an iterable:
print('dog'[3])
IndentationError
– Raised when indentation is incorrect:
for i in range(3): print(i)
ZeroDivisionError
– Raised at attempt to divide a number by zero:
print(1/0)
ImportError
– Raised when an import statement is incorrect:
from numpy import pandas
AttributeError
– Raised at attempt to assign or refer an attribute inapplicable for a given Python object:
print('a'.sum())
KeyError
– Raised when the key is absent in the dictionary:
animals = {'koala': 1, 'panda': 2} print(animals['rabbit'])
For a full list of Python built-in exceptions, please consult the Python Documentation.
Handling Exceptions in Python
Since raising an exception results in an interruption of the program execution, we have to handle this exception in advance to avoid such undesirable cases.
The try
and except
Statements
The most basic commands used for detecting and handling exceptions in Python are try
and except
.
The try
statement is used to run an error-prone piece of code and must always be followed by the except
statement. If no exception is raised as a result of the try
block execution, the except
block is skipped and the program just runs as expected. In the opposite case, if an exception is thrown, the execution of the try
block is immediately stopped, and the program handles the raised exception by running the alternative code determined in the except
block. After that, the Python script continues working and executes the rest of the code.
Let’s see how it works by the example of our initial small piece of code print(x)
, which raised earlier a NameError
:
try: print(x) except: print('Please declare the variable x first') print(1)
Now that we handled the exception in the except
block,
we received a meaningful customized message of what exactly went wrong
and how to fix it. What’s more, this time, the program didn’t stop
working as soon as it encountered the exception and executed the rest of
the code.
In the above case, we anticipated and handled only one type of exception, more specifically, a NameError
.
The drawback of this approach is that the piece of code in the except
clause will treat all types of
exceptions in the same way, and output the same message Please declare the variable x first
. To avoid
this confusion, we can explicitly mention the type of exception that we need to catch and handle right after the except
command:
try: print(x) except NameError: print('Please declare the variable x first')
Handling Multiple Exceptions
Clearly stating the exception type to be caught is needed not only for the sake of code readability. What’s more important, using this approach, we can anticipate various specific exceptions and handle them accordingly.
To understand this concept, let’s take a look at a simple function that sums up the values of an input dictionary:
def print_dict_sum(dct): print(sum(dct.values())) my_dict = {'a': 1, 'b': 2, 'c': 3} print_dict_sum(my_dict)
Trying to run this function, we can have different issues if we accidentally pass to it a wrong input. For example, we can make an error in the dictionary name resulting in an inexistent variable:
def print_dict_sum(dct): print(sum(dct.values())) my_dict = {'a': 1, 'b': 2, 'c': 3} print_dict_sum(mydict)
Some of the values of the input dictionary can be a string rather than numeric:
def print_dict_sum(dct): print(sum(dct.values())) my_dict = {'a': '1', 'b': 2, 'c': 3} print_dict_sum(my_dict)
Another option allows us to pass in an argument of an inappropriate data type for this function:
def print_dict_sum(dct): print(sum(dct.values())) my_dict = 'a' print_dict_sum(my_dict)
As a result, we have at least three different types of exceptions that should be handled differently:
NameError
, TypeError
, and AttributeError
. For this purpose,
we can add multiple except
blocks (one for each exception type, three in our case) after
a single try
block:
def print_dict_sum(dct): print(sum(dct.values())) #my_dict = 'a' try: print_dict_sum(mydict) except NameError: print('Please check the spelling of the dictionary name') except TypeError: print('It seems that some of the dictionary values are not numeric') except AttributeError: print('You should provide a Python dictionary with numeric values')
In the code above, we provided a nonexistent variable name as an input to the function inside
the try
clause. The code was supposed to throw a NameError
but it was handled
in one of the subsequent except
clauses, and the corresponding message was output.
We can also handle the exceptions right inside the function definition. Important:
We can’t handle a NameError
exception for any of the function arguments since in this case, the
exception happens before the function body starts. For example, in the
code below:
def print_dict_sum(dct): try: print(sum(dct.values())) except NameError: print('Please check the spelling of the dictionary name') except TypeError: print('It seems that some of the dictionary values are not numeric') except AttributeError: print('You should provide a Python dictionary with numeric values') print_dict_sum({'a': '1', 'b': 2, 'c': 3}) print_dict_sum('a') print_dict_sum(mydict)
The TypeError
and AttributeError
were
successfully handled inside the function and the corresponding messages
were output. Instead, because of the above-mentioned reason, the NameError
was not handled properly despite introducing a separate except
clause for it. Therefore, the NameError
for any argument of a function can’t be handled inside the function body.
It is possible to combine several exceptions as a tuple in one except
clause if they should be handled in the same way:
def print_dict_sum(dct): try: print(sum(dct.values())) except (TypeError, AttributeError): print('You should provide a Python DICTIONARY with NUMERIC values') print_dict_sum({'a': '1', 'b': 2, 'c': 3}) print_dict_sum('a')
The else
Statement
In addition to the try
and except
clauses, we can use an optional
else
command. If present, the else
command must be placed after all the
except
clauses and executed only if no exceptions occurred in the try
clause.
For example, in the code below, we tried the division by zero:
try: print(3/0) except ZeroDivisionError: print('You cannot divide by zero') else: print('The division is successfully performed')
The exception was caught and handled in the except block and hence the else clause was skipped. Let’s take a look at what happens if we provide a non-zero number:
try: print(3/2) except ZeroDivisionError: print('You cannot divide by zero') else: print('The division is successfully performed')
Since no exception was raised, the else
block was executed and output the corresponding message.
The finally
Statement
Another optional statement is finally
, if provided, it must be placed after all the
clauses including else
(if present)
and executed in any case, whether or not an exception was raised in the try
clause.
Let’s add the finally
block to both of the previous pieces of code and observe the results:
try: print(3/0) except ZeroDivisionError: print('You cannot divide by zero') else: print('The division is successfully performed') finally: print('This message is always printed')
try: print(3/2) except ZeroDivisionError: print('You cannot divide by zero') else: print('The division is successfully performed') finally: print('This message is always printed')
In the first case, an exception was raised, in the second there was not. However, in both cases, the finally
clause outputs the same message.
Raising an Exception
Sometimes, we may need to deliberately raise an exception and stop
the program if a certain condition occurs. For this purpose, we need the
raise
keyword and the following syntax:
raise ExceptionClass(exception_value)
Above, ExceptionClass
is the type of exception to be raised (e.g., TypeError
) and exception_value
is an optional customized descriptive message that will be displayed if the exception is raised.
Let’s see how it works:
x = 'blue' if x not in ['red', 'yellow', 'green']: raise ValueError
In the piece of code above, we didn’t provide any argument to the
exception and therefore the code didn’t output any message (by default,
the exception value is None
).
x = 'blue' if x not in ['red', 'yellow', 'green']: raise ValueError('The traffic light is broken')
We ran the same piece of code, but this time we provided the exception argument. In this case, we can see an output message that gives more context to why exactly this exception occurred.
Conclusion
In this tutorial, we discussed many aspects regarding exceptions in Python. In particular, we learned the following:
- How to define exceptions in Python and how they differ from syntax errors
- What built-in exceptions exist in Python and when they are raised
- Why it is important to catch and handle exceptions
- How to handle one or multiple exceptions in Python
- How different clauses for catching and handling exceptions work together
- Why it’s important to specify the type of exception to be handled
- Why we can’t handle a
NameError
for any argument of a function inside the function definition - How to raise an exception
- How to add a descriptive message to a raised exception and why it is a good practice
Nächste Einheit: 07 Grafische Ausgaben