In this article, let us learn about printing error messages from Exceptions with the help of 5 specifically chosen examples.
I have divided this article into 2 major sections
- Printing custom error messages and
- Printing a specific part of the default error message. By “default error message“, I mean the error message that you typically get in the command line if you did not catch a given exception)
Depending on which of the 2 options above you are looking for, you can jump to the respective section of the article using the table of content below.
So, let’s begin!
Printing Custom Error messages
There are 3 ways to print custom error messages in Python. Let us start with the simplest of the 3, which is using a print() statement.
Option#1: Using a simple print() statement
The first and easiest option is to print error messages using a simple print() statement as shown in the example below.
try:
#Some Problematic code that can produce Exceptions
x = 5/0
except Exception as e:
print('A problem has occurred from the Problematic code: ', e)
Running this code will give the output below.
A problem has occurred from the Problematic code: division by zero
Here the line “x = 5/0″ in Example 1 above raised a “ZeroDivisionError” which was caught by our except clause and the print() statement printed the default error message which is “division by zero” to the standard output.
One thing to note here is the line “except Exception as e“. This line of code’s function is to catch all possible exceptions, whichever occurs first as an “Exception” object. This object is stored in the variable “e” (line 4), which returns the string ‘division by zero‘ when used with the print() statement (line 5).
To summarize if you wish to print out the default error message along with a custom message use Option#1.
This is the simplest way to print error messages in python. But this option of putting your custom messages into print statements might not work in cases where you might be handling a list of exceptions using a single except clause. If you are not exactly sure how to catch a list of exceptions using a single except clause, I suggest reading my other article in the link below.
Python: 3 Ways to Catch Multiple Exceptions in a single “except” clause
There I have explained the 3 ways through which you can catch a list of exceptions along with tips on when is the right situation to catch each of these exceptions.
Now that we have learned how to print the default string which comes with an exception object, let us next learn how to customize the message that e carried (the string ‘division by zero‘) and replace that with our own custom error message.
Option#2: Using Custom Exception classes to get customized error messages
In Python, you can define your own custom exception classes by inheriting from another Exception class as shown in the code below.
class MyOwnException(Exception):
def __str__(self):
return 'My Own Exception has occurred'
def __repr__(self):
return str(type(self))
try:
raise MyOwnException
except MyOwnException as e:
print(e)
print(repr(e))
How to choose the exception class to inherit from?
In the above example, I have inherited from the Exception class in python, but the recommended practice is to choose a class that closely resembles your use-case.
For example, say you are trying to work with a string type object and you are given a list type object instead, here you should inherit your custom exception from TypeError since this Exception type closely resembles your use case which is “the variable is not of expected type”.
If you are looking for getting an appropriate Exception class to inherit from, I recommend having a look at all the built-in exceptions from the official python page here. For the sake of keeping this example simple, I have chosen the higher-level exception type named “Exception” class to inherit from.
In the code below, we are collecting values from the user and to tell the user that there is an error in the value entered we are using the ValueError class.
class EnteredGarbageError(ValueError):
def __str__(self):
return 'You did not select an option provided!'
try:
options = ['A', 'B', 'C']
x = input('Type A or B or C: ')
if x not in options:
raise EnteredGarbageError
else:
print ('You have chosen: ', x)
except EnteredGarbageError as err:
print(err)
Now that we understand how to choose a class to inherit from, let us next have a look at how to customize the default error messages that these classes return.
How to customize the error message in our custom exception class?
To help us achieve our purpose here which is to print some custom error messages, all objects in python come with 2 methods named __str__ and __repr__. This is pronounced “dunder-str” and “dunder-repr” where “dunder” is short for “double underscore”.
Dunder-str method:
The method __str__ returns a string and this is what the built-in print() function calls whenever we pass it an object to print.
print(object1)
In the line above, python will call the __str__ method of the object and prints out the string returned by that method.
Let us have a look at what python’s official documentation over at python.org has to say about the str method.
In simpler words, the str method returns a human-readable string for logging purposes, and when this information is passed to the built-in function print(), the string it returns gets printed.
So since our implementation of str returns the string “My Own Exception has occurred” this string got printed on the first line of the exception message.
Dunder-repr method:
__repr__ is another method available in all objects in python.
Where it differs from the dunder-str method is the fact that while the __str__ is used for getting a “friendly message”, the __repr__ method is used for getting, a more of a, “formal message”. You can think of str as a text you got from your friends and repr as a notice you got from a legal representative!
The below screenshot from python’s official documentation explains the use of __repr__ method.
Again, in simpler words, repr is typically used to print some “formal” or “official” information about an object in Python
In our Example 2 above, the repr method returned the class name using the built-in type() function.
Next, let us see another variation where we can print different error messages using a single Exception class without making a custom class.
Option#3: Custom Error messages from the raise statement
try:
raise Exception('I wish to print this message')
except Exception as error:
print(error)
Lucky for us, python has made this process incredibly simple! Just pass in the message as an argument to the type of exception you wish to raise and this will print that custom message instead!
In the above code, we are throwing an exception of type “Exception” by calling its constructor and giving the custom message as an argument, which then overrides the default __str__ method to return the string passed in.
If you wish to learn more about raise statement, I suggest reading my other article in the link below
Python: Manually throw/raise an Exception using the “raise” statement
where I have explained 3 ways you can use the raise statement in python and when to use each.
But when to use option 2 and when to use option 3?
On the surface, Option#3 of passing in the custom message may look like it made option#2 of using custom classes useless. But the main reason to use Option#2 is the fact that Option#2 can be used to override more than just the __str__ method.
Let’s next move on to section 2 of this article and look at how to choose a specific part of the default error message (the error printed on the console when you don’t catch an exception) and use that to make our own error messages
Choosing Parts of Default Error Messages to print
To understand what I mean by “Default Error Message” let us see an example
raise ValueError("This is an ValueError")
This line when run, will print the following error message
Traceback (most recent call last):
File "<ipython-input-24-57127e33a735>", line 1, in <module>
raise ValueError("This is an ValueError")
ValueError: This is an ValueError
This error message contains 3 Parts
- Exception Type (ValueError)
- Error message (This is an ValueError)
- and the stack trace (the 1st few lines showing us where exactly in the program the exception has occurred)
The information needed
- to extract and use each of the individual pieces of information listed above and
- when to use what piece of information
is already covered with the help of several examples in my previous article in the link below
Python Exceptions: Getting and Handling Error Messages as strings.
And with that I will end this article!
If you are looking for another interesting read, try the article in the link below.
Exceptions in Python: Everything You Need To Know!
The above article covers all the basics of Exception handling in Python like
- when and how to ignore exceptions
- when and how to retry the problematic code that produced the exception and
- when and how to log the errors
I hope you enjoyed reading this article and got some value from it.
Feel free to share it with your friends and colleagues!
An Exception is raised whenever there is an error encountered, and it signifies that something went wrong with the program. By default, there are many exceptions that the language defines for us, such as TypeError
when the wrong type is passed. In this article, we shall look at how we can create our own Custom Exceptions in Python.
But before we take a look at how custom exceptions are implemented, let us find out how we could raise different types of exceptions in Python.
Raise Exceptions
Python allows the programmer to raise an Exception manually using the raise
keyword.
Format: raise ExceptionName
The below function raises different exceptions depending on the input passed to the function.
def exception_raiser(string): if isinstance(string, int): raise ValueError elif isinstance(string, str): raise IndexError else: raise TypeError
Output:
>>> exception_raiser(123) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in exception_raiser ValueError >>> exception_raiser('abc') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in exception_raiser IndexError >>> exception_raiser([123, 456]) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in exception_raiser TypeError
As you can observe, different types of Exceptions are raised based on the input, at the programmer’s choice. This allows for good flexibility of Error Handling as well, since we can actively predict why an Exception can be raised.
Defining Custom Exceptions
Similarly, Python also allows us to define our own custom Exceptions. We are in complete control of what this Exception can do, and when it can be raised, using the raise
keyword. Let us look at how we can define and implement some custom Exceptions.
1. Create a Custom Exception Class
We can create a custom Exception class to define the new Exception. Again, the idea behind using a Class is because Python treats everything as a Class. So it doesn’t seem that outlandish that an Exception can be a class as well!
All Exceptions inherit the parent Exception
Class, which we shall also inherit when creating our class.
We shall create a Class called MyException
, which raises an Exception only if the input passed to it is a list and the number of elements in the list is odd.
class MyException(Exception): pass def list_check(lst): if len(lst) % 2 != 0: raise MyException # MyException will not be raised list_check([1, 2, 3, 4]) # MyException will be raised list_check([1, 3, 5])
Output:
[email protected]:~# python3 exceptions.py Traceback (most recent call last): File "exceptions.py", line 12, in <module> list_check([1, 3, 5]) File "exceptions.py", line 6, in list_check raise MyException __main__.MyException
2. Add a custom Message and Error
We can add our own error messages and print them to the console for our Custom Exception. This involves passing two other parameters in our MyException
class, the message
and error
parameters.
Let us modify our original code to account for a custom Message and Error for our Exception.
class MyException(Exception): def __init__(self, message, errors): # Call Exception.__init__(message) # to use the same Message header as the parent class super().__init__(message) self.errors = errors # Display the errors print('Printing Errors:') print(errors) def list_check(lst): if len(lst) % 2 != 0: raise MyException('Custom Message', 'Custom Error') # MyException will not be raised list_check([1, 2, 3, 4]) # MyException will be raised list_check([1, 3, 5])
Output:
Printing Errors: Custom Error Traceback (most recent call last): File "exceptions.py", line 17, in <module> list_check([1, 3, 5]) File "exceptions.py", line 11, in list_check raise MyException('Custom Message', 'Custom Error') __main__.MyException: Custom Message
We have thus successfully implemented our own Custom Exceptions, including adding custom error messages for debugging purposes! This can be very useful if you are building a Library/API and another programmer wants to know what exactly went wrong when the custom Exception is raised.
Conclusion
In this article, we learned how to raise Exceptions using the raise
keyword, and also build our own Exceptions using a Class and add error messages to our Exception.
References
- JournalDev article on Custom Exceptions
- Exception Handling in Python
Exceptions
So far we have made programs that ask the user to enter a string, and
we also know how to convert that to an integer.
text = input("Enter something: ") number = int(text) print("Your number doubled:", number*2)
That works.
Enter a number: 3
Your number doubled: 6
But that doesn’t work if the user does not enter a number.
Enter a number: lol Traceback (most recent call last): File "/some/place/file.py", line 2, in <module> number = int(text) ValueError: invalid literal for int() with base 10: 'lol'
So how can we fix that?
What are exceptions?
In the previous example we got a ValueError. ValueError is an
exception. In other words, ValueError is an error that can occur
in our program. If an exception occurs, the program will stop and we
get an error message. The interactive prompt will display an error
message and keep going.
>>> int('lol') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: 'lol' >>>
Exceptions are classes.
>>> ValueError <class 'ValueError'> >>>
We can also create exceptions. We won’t get an error message by doing
that, but we’ll use this for displaying our own error messages later.
>>> the_problem = ValueError('oh no') >>> the_problem ValueError('oh no',) >>>
Catching exceptions
If we need to try to do something and see if we get an exception, we
can use try
and except
. This is also known as catching the
exception.
>>> try: ... print(int('lol')) ... except ValueError: ... print("Oops!") ... Oops! >>>
The except part doesn’t run if the try part succeeds.
>>> try: ... print("Hello World!") ... except ValueError: ... print("What the heck? Printing failed!") ... Hello World! >>>
ValueError is raised when something gets an invalid value, but the
value’s type is correct. In this case, int
can take a string as an
argument, but the string needs to contain a number, not lol
. If
the type is wrong, we will get a TypeError instead.
>>> 123 + 'hello' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str' >>>
Exceptions always interrupt the code even if we catch them. Here the
print never runs because it’s after the error but inside the try
block. Everything after the try block runs normally.
>>> try: ... 123 + 'hello' ... print("This doesn't get printed.") ... except TypeError: ... print("Oops!") ... Oops! >>>
Does an except ValueError
also catch TypeErrors?
>>> try: ... print(123 + 'hello') ... except ValueError: ... print("Oops!") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str' >>>
No, it doesn’t. But maybe we could except for both ValueError and
TypeError?
>>> try: ... int('lol') ... except ValueError: ... print('wrong value') ... except TypeError: ... print('wrong type') ... wrong value >>> try: ... 123 + 'hello' ... except ValueError: ... print('wrong value') ... except TypeError: ... print('wrong type') ... wrong type >>>
Seems to be working.
We can also also catch multiple exceptions by catching
a tuple of exceptions:
>>> try: ... 123 + 'hello' ... except (ValueError, TypeError): ... print('wrong value or type') ... wrong value or type >>> try: ... int('lol') ... except (ValueError, TypeError): ... print('wrong value or type') ... wrong value or type >>>
Catching Exception
will catch all errors. We’ll learn more about why
it does that in a moment.
>>> try: ... 123 + 'hello' ... except Exception: ... print("Oops!") ... Oops! >>> try: ... int('lol') ... except Exception: ... print("Oops!") ... Oops! >>>
It’s also possible to catch an exception and store it in a variable.
Here we are catching an exception that Python created and storing it in
our_error
.
>>> try: ... 123 + 'hello' ... except TypeError as e: ... our_error = e ... >>> our_error TypeError("unsupported operand type(s) for +: 'int' and 'str'",) >>> type(our_error) <class 'TypeError'> >>>
When should we catch exceptions?
Do not do things like this:
try: # many lines of code except Exception: print("Oops! Something went wrong.")
There’s many things that can go wrong in the try
block. If something
goes wrong all we have is an oops message that doesn’t tell us which
line caused the problem. This makes fixing the program really annoying.
If we want to catch exceptions we need to be specific about what exactly
we want to catch and where instead of catching everything we can in the
whole program.
There’s nothing wrong with doing things like this:
try: with open('some file', 'r') as f: content = f.read() except OSError: # we can't read the file but we can work without it content = some_default_content
Usually catching errors that the user has caused is also a good idea:
import sys text = input("Enter a number: ") try: number = int(text) except ValueError: print(f"'{text}' is not a number.", file=sys.stderr) sys.exit(1) print(f"Your number doubled is {(number * 2)}.")
Raising exceptions
Now we know how to create exceptions and how to handle errors that
Python creates. But we can also create error messages manually. This
is known as raising an exception and throwing an exception.
Raising an exception is easy. All we need to do is to type raise
and then an exception we want to raise:
>>> raise ValueError("lol is not a number") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: lol is not a number >>>
Of course, we can also raise an exception from a variable.
>>> oops = ValueError("lol is not a number") >>> raise oops Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: lol is not a number >>>
If we define a function that raises an
exception and call it we’ll notice that the error message also
says which functions we ran to get to that error.
>>> def oops(): ... raise ValueError("oh no!") ... >>> def do_the_oops(): ... oops() ... >>> do_the_oops() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in do_the_oops File "<stdin>", line 2, in oops ValueError: oh no! >>>
If our code was in a file we would also see the line of code
that raised the error.
When should we raise exceptions?
Back in the module chapter we learned to display error
messages by printing to sys.stderr
and then calling sys.exit(1)
, so
when should we use that and when should we raise an exception?
Exceptions are meant for programmers, so if we are writing something
that other people will import we should use exceptions. If our program
is working like it should be and the user has done something wrong,
it’s usually better to use sys.stderr
and sys.exit
.
Exception hierarchy
Exceptions are organized like this. I made this tree with this
program on Python 3.7. You may
have more or less exceptions than I have if your Python is newer or
older than mine, but they should be mostly similar.
Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ImportError
│ └── ModuleNotFoundError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ ├── BlockingIOError
│ ├── ChildProcessError
│ ├── ConnectionError
│ │ ├── BrokenPipeError
│ │ ├── ConnectionAbortedError
│ │ ├── ConnectionRefusedError
│ │ └── ConnectionResetError
│ ├── FileExistsError
│ ├── FileNotFoundError
│ ├── InterruptedError
│ ├── IsADirectoryError
│ ├── NotADirectoryError
│ ├── PermissionError
│ ├── ProcessLookupError
│ └── TimeoutError
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ └── RecursionError
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
│ └── IndentationError
│ └── TabError
├── SystemError
├── TypeError
├── ValueError
│ └── UnicodeError
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
└── Warning
├── BytesWarning
├── DeprecationWarning
├── FutureWarning
├── ImportWarning
├── PendingDeprecationWarning
├── ResourceWarning
├── RuntimeWarning
├── SyntaxWarning
├── UnicodeWarning
└── UserWarning
Catching an exception also catches everything that’s under it in this
tree. For example, catching OSError
catches errors that we typically
get when processing files, and catching Exception catches
all of these errors. You don’t need to remember this tree, running
help('builtins')
should display a larger tree that this is a part of.
There are also a few exceptions that are not in this tree like
SystemExit and KeyboardInterrupt, but most of the time we shouldn’t
catch them. Catching Exception doesn’t catch them either.
Summary
- Exceptions are classes and they can be used just like all other classes.
- ValueError and TypeError are some of the most commonly used exceptions.
- The
try
andexcept
keywords can be used for attempting to do
something and then doing something else if we get an error. This is
known as catching exceptions. - It’s possible to raise exceptions with the
raise
keyword. This
is also known as throwing exceptions. - Raise exceptions if they are meant to be displayed for programmers and
usesys.stderr
andsys.exit
otherwise.
Examples
Keep asking a number from the user until it’s entered correctly.
while True: try: number = int(input("Enter a number: ")) break except ValueError: print("That's not a valid number! Try again.") print("Your number doubled is:", number * 2)
This program allows the user to customize the message it prints by
modifying a file the greeting is stored in, and it can create the
file for the user if it doesn’t exist already. This example also uses
things from the file chapter, the function defining
chapter and the module chapter.
# These are here so you can change them to customize the program # easily. default_greeting = "Hello World!" filename = "greeting.txt" import sys def askyesno(question): while True: answer = input(question + ' (y or n) ') if answer == 'Y' or answer == 'y': return True if answer == 'N' or answer == 'n': return False def greet(): with open(filename, 'r') as f: for line in f: print(line.rstrip('n')) try: greet() except OSError: print(f"Cannot read '{filename}'!", file=sys.stderr) if askyesno("Would you like to create a default greeting file?"): with open(filename, 'w') as f: print(default_greeting, file=f) greet()
If you have trouble with this tutorial, please
tell me about it and I’ll make this tutorial better,
or ask for help online.
If you like this tutorial, please give it a
star.
You may use this tutorial freely at your own risk. See
LICENSE.
Previous | Next |
List of contents
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Raising and Handling Python Exceptions
A Python program terminates as soon as it encounters an error. In Python, an error can be a syntax error or an exception. In this article, you will see what an exception is and how it differs from a syntax error. After that, you will learn about raising exceptions and making assertions. Then, you’ll finish with a demonstration of the try and except block.
Exceptions versus Syntax Errors
Syntax errors occur when the parser detects an incorrect statement. Observe the following example:
>>> print( 0 / 0 ))
File "<stdin>", line 1
print( 0 / 0 ))
^
SyntaxError: invalid syntax
The arrow indicates where the parser ran into the syntax error. In this example, there was one bracket too many. Remove it and run your code again:
>>> print( 0 / 0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
This time, you ran into an exception error. This type of error occurs whenever syntactically correct Python code results in an error. The last line of the message indicated what type of exception error you ran into.
Instead of showing the message exception error
, Python details what type of exception error was encountered. In this case, it was a ZeroDivisionError
. Python comes with various built-in exceptions as well as the possibility to create self-defined exceptions.
Raising an Exception
We can use raise
to throw an exception if a condition occurs. The statement can be complemented with a custom exception.
If you want to throw an error when a certain condition occurs using raise
, you could go about it like this:
x = 10
if x > 5:
raise Exception('x should not exceed 5. The value of x was: {}'.format(x))
When you run this code, the output will be the following:
Traceback (most recent call last):
File "<input>", line 4, in <module>
Exception: x should not exceed 5. The value of x was: 10
The program comes to a halt and displays our exception to screen, offering clues about what went wrong.
The AssertionError
Exception
Instead of waiting for a program to crash midway, you can also start by making an assertion in Python. We assert
that a certain condition is met. If this condition turns out to be True
, then that is excellent! The program can continue. If the condition turns out to be False
, you can have the program throw an AssertionError
exception.
Have a look at the following example, where it is asserted that the code will be executed on a Linux system:
import sys
assert ('linux' in sys.platform), "This code runs on Linux only."
If you run this code on a Linux machine, the assertion passes. If you were to run this code on a Windows machine, the outcome of the assertion would be False
and the result would be the following:
Traceback (most recent call last):
File "<input>", line 2, in <module>
AssertionError: This code runs on Linux only.
In this example, throwing an AssertionError
exception is the last thing that the program will do. The program will come to halt and will not continue. What if that is not what you want?
The try
and except
Block: Handling Exceptions
The try
and except
block in Python is used to catch and handle exceptions. Python executes code following the try
statement as a “normal” part of the program. The code that follows the except
statement is the program’s response to any exceptions in the preceding try
clause.
As you saw earlier, when syntactically correct code runs into an error, Python will throw an exception error. This exception error will crash the program if it is unhandled. The except
clause determines how your program responds to exceptions.
The following function can help you understand the try
and except
block:
def linux_interaction():
assert ('linux' in sys.platform), "Function can only run on Linux systems."
print('Doing something.')
The linux_interaction()
can only run on a Linux system. The assert
in this function will throw an AssertionError
exception if you call it on an operating system other then Linux.
You can give the function a try
using the following code:
try:
linux_interaction()
except:
pass
The way you handled the error here is by handing out a pass
. If you were to run this code on a Windows machine, you would get the following output:
You got nothing. The good thing here is that the program did not crash. But it would be nice to see if some type of exception occurred whenever you ran your code. To this end, you can change the pass
into something that would generate an informative message, like so:
try:
linux_interaction()
except:
print('Linux function was not executed')
Execute this code on a Windows machine:
Linux function was not executed
When an exception occurs in a program running this function, the program will continue as well as inform you about the fact that the function call was not successful.
What you did not get to see was the type of error that was thrown as a result of the function call. In order to see exactly what went wrong, you would need to catch the error that the function threw.
The following code is an example where you capture the AssertionError
and output that message to screen:
try:
linux_interaction()
except AssertionError as error:
print(error)
print('The linux_interaction() function was not executed')
Running this function on a Windows machine outputs the following:
Function can only run on Linux systems.
The linux_interaction() function was not executed
The first message is the AssertionError
, informing you that the function can only be executed on a Linux machine. The second message tells you which function was not executed.
In the previous example, you called a function that you wrote yourself. When you executed the function, you caught the AssertionError
exception and printed it to screen.
Here’s another example where you open a file and use a built-in exception:
try:
with open('file.log') as file:
read_data = file.read()
except:
print('Could not open file.log')
If file.log does not exist, this block of code will output the following:
This is an informative message, and our program will still continue to run. In the Python docs, you can see that there are a lot of built-in exceptions that you can use here. One exception described on that page is the following:
Exception
FileNotFoundError
Raised when a file or directory is requested but doesn’t exist. Corresponds to errno ENOENT.
To catch this type of exception and print it to screen, you could use the following code:
try:
with open('file.log') as file:
read_data = file.read()
except FileNotFoundError as fnf_error:
print(fnf_error)
In this case, if file.log does not exist, the output will be the following:
[Errno 2] No such file or directory: 'file.log'
You can have more than one function call in your try
clause and anticipate catching various exceptions. A thing to note here is that the code in the try
clause will stop as soon as an exception is encountered.
Look at the following code. Here, you first call the linux_interaction()
function and then try to open a file:
try:
linux_interaction()
with open('file.log') as file:
read_data = file.read()
except FileNotFoundError as fnf_error:
print(fnf_error)
except AssertionError as error:
print(error)
print('Linux linux_interaction() function was not executed')
If the file does not exist, running this code on a Windows machine will output the following:
Function can only run on Linux systems.
Linux linux_interaction() function was not executed
Inside the try
clause, you ran into an exception immediately and did not get to the part where you attempt to open file.log. Now look at what happens when you run the code on a Linux machine:
[Errno 2] No such file or directory: 'file.log'
Here are the key takeaways:
- A
try
clause is executed up until the point where the first exception is encountered. - Inside the
except
clause, or the exception handler, you determine how the program responds to the exception. - You can anticipate multiple exceptions and differentiate how the program should respond to them.
- Avoid using bare
except
clauses.
The else
Clause
In Python, using the else
statement, you can instruct a program to execute a certain block of code only in the absence of exceptions.
Look at the following example:
try:
linux_interaction()
except AssertionError as error:
print(error)
else:
print('Executing the else clause.')
If you were to run this code on a Linux system, the output would be the following:
Doing something.
Executing the else clause.
Because the program did not run into any exceptions, the else
clause was executed.
You can also try
to run code inside the else
clause and catch possible exceptions there as well:
try:
linux_interaction()
except AssertionError as error:
print(error)
else:
try:
with open('file.log') as file:
read_data = file.read()
except FileNotFoundError as fnf_error:
print(fnf_error)
If you were to execute this code on a Linux machine, you would get the following result:
Doing something.
[Errno 2] No such file or directory: 'file.log'
From the output, you can see that the linux_interaction()
function ran. Because no exceptions were encountered, an attempt to open file.log was made. That file did not exist, and instead of opening the file, you caught the FileNotFoundError
exception.
Cleaning Up After Using finally
Imagine that you always had to implement some sort of action to clean up after executing your code. Python enables you to do so using the finally
clause.
Have a look at the following example:
try:
linux_interaction()
except AssertionError as error:
print(error)
else:
try:
with open('file.log') as file:
read_data = file.read()
except FileNotFoundError as fnf_error:
print(fnf_error)
finally:
print('Cleaning up, irrespective of any exceptions.')
In the previous code, everything in the finally
clause will be executed. It does not matter if you encounter an exception somewhere in the try
or else
clauses. Running the previous code on a Windows machine would output the following:
Function can only run on Linux systems.
Cleaning up, irrespective of any exceptions.
Summing Up
After seeing the difference between syntax errors and exceptions, you learned about various ways to raise, catch, and handle exceptions in Python. In this article, you saw the following options:
raise
allows you to throw an exception at any time.assert
enables you to verify if a certain condition is met and throw an exception if it isn’t.- In the
try
clause, all statements are executed until an exception is encountered. except
is used to catch and handle the exception(s) that are encountered in the try clause.else
lets you code sections that should run only when no exceptions are encountered in the try clause.finally
enables you to execute sections of code that should always run, with or without any previously encountered exceptions.
Hopefully, this article helped you understand the basic tools that Python has to offer when dealing with exceptions.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Raising and Handling Python Exceptions
Время прочтения
10 мин
Просмотры 31K
Одним из недостатков гибких языков, таких как Python, является предположение, что если что-то работает, то скорее всего оно сделано правильно. Я хочу написать скромное руководство по эффективному использованию исключений в Python, правильной их обработке и логировании.
Эффективная обработка исключений
Введение
Давайте рассмотрим следующую систему. У нас есть микросервис, который отвечает за:
· Прослушивание событий о новом заказе;
· Получение заказа из базы данных;
· Проверку состояния принтера;
· Печать квитанции;
· Отправка квитанции в налоговую систему (IRS).
В любой момент может сломаться что угодно. У вас могут возникнуть проблемы с объектом заказа, в котором может не быть нужной информации, или в принтере может закончиться бумага, или же сервис налоговой не будет работать, и вы не сможете синхронизировать с ними квитанцию об оплате, а может быть ваша база данных окажется недоступна.
Ваша задача правильно и проактивно реагировать на любую ситуацию, чтобы избежать ошибок при обработке новых заказов.
И примерно вот такой код на этот случай пишут люди (он, конечно, работает, но плохо и неэффективно):
class OrderService:
def emit(self, order_id: str) -> dict:
try:
order_status = status_service.get_order_status(order_id)
except Exception as e:
logger.exception(
f"Order {order_id} was not found in db "
f"to emit. Error: {e}."
)
raise e
(
is_order_locked_in_emission,
seconds_in_emission,
) = status_service.is_order_locked_in_emission(order_id)
if is_order_locked_in_emission:
logger.info(
"Redoing emission because "
"it was locked in that state after a long time! "
f"Time spent in that state: {seconds_in_emission} seconds "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
elif order_status == OrderStatus.EMISSION_IN_PROGRESS:
logger.info("Aborting emission request because it is already in progress!")
return {"order_id": order_id, "order_status": order_status.value}
elif order_status == OrderStatus.EMISSION_SUCCESSFUL:
logger.info(
"Aborting emission because it already happened! "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
return {"order_id": order_id, "order_status": order_status.value}
try:
receipt_note = receipt_service.create(order_id)
except Exception as e:
logger.exception(
"Error found during emission! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise e
try:
broker.emit_receipt_note(receipt_note)
except Exception as e:
logger.exception(
"Emission failed! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise e
order_status = status_service.get_order_status(order_id)
return {"order_id": order_id, "order_status": order_status.value}
Сначала я сосредоточусь на том, что OrderService
слишком много знает, и все эти данные делают его чем-то вроде blob, а чуть позже расскажу о правильной обработке и правильном логировании исключений.
Почему этот сервис — blob?
Этот сервис знает слишком много. Кто-то может сказать, что он знает только то, что ему нужно (то есть все шаги, связанные с формированием чека), но на самом деле он знает куда больше.
Он сосредоточен на создании ошибок (например, база данных, печать, статус заказа), а не на том, что он делает (например, извлекает, проверяет статус, генерирует, отправляет) и на том, как следует реагировать в случае сбоев.
В этом смысле мне кажется, что клиент учит сервис тому, какие исключения он может выдать. Если мы решим переиспользовать его на любом другом этапе (например, клиент захочет получить еще одну печатную копию более старого чека по заказу), мы скопируем большую часть этого кода.
Несмотря на то, что сервис работает нормально, поддерживать его трудно, и неясно, как один шаг соотносится с другим из-за повторяющихся блоков except между шагами, которые отвлекают наше внимание на вопрос «как» вместо того, чтобы думать о «когда».
Первое улучшение: делайте исключения конкретными
Давайте сначала сделаем исключения более точными и конкретными. Преимущества не видны сразу, поэтому я не буду тратить слишком много времени на объяснение этого прямо сейчас. Однако обратите внимание на то, как изменяется код.
Я выделю только то, что мы поменяли:
try:
order_status = status_service.get_order_status(order_id)
except Exception as e:
logger.exception(...)
raise OrderNotFound(order_id) from e
...
try:
...
except Exception as e:
logger.exception(...)
raise ReceiptGenerationFailed(order_id) from e
try:
broker.emit_receipt_note(receipt_note)
except Exception as e:
logger.exception(...)
raise ReceiptEmissionFailed(order_id) from e
Обратите внимание, что на этот раз я пользуюсь from e
, что является правильным способом создания одного исключения из другого и сохраняет полную трассировку стека.
Второе улучшение: не лезьте не в свое дело
Теперь, когда у нас есть кастомные исключения, мы можем перейти к мысли «не учите классы тому, что может пойти не так» — они сами скажут, если это случится!
# Services
class StatusService:
def get_order_status(order_id):
try:
...
except Exception as e:
raise OrderNotFound(order_id) from e
class ReceiptService:
def create(order_id):
try:
...
except Exception as e:
raise ReceiptGenerationFailed(order_id) from e
class Broker:
def emit_receipt_note(receipt_note):
try:
...
except Exception as e:
raise ReceiptEmissionFailed(order_id) from e
# Main class
class OrderService:
def emit(self, order_id: str) -> dict:
try:
order_status = status_service.get_order_status(order_id)
(
is_order_locked_in_emission,
seconds_in_emission,
) = status_service.is_order_locked_in_emission(order_id)
if is_order_locked_in_emission:
logger.info(
"Redoing emission because "
"it was locked in that state after a long time! "
f"Time spent in that state: {seconds_in_emission} seconds "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
elif order_status == OrderStatus.EMISSION_IN_PROGRESS:
logger.info("Aborting emission request because it is already in progress!")
return {"order_id": order_id, "order_status": order_status.value}
elif order_status == OrderStatus.EMISSION_SUCCESSFUL:
logger.info(
"Aborting emission because it already happened! "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
return {"order_id": order_id, "order_status": order_status.value}
receipt_note = receipt_service.create(order_id)
broker.emit_receipt_note(receipt_note)
order_status = status_service.get_order_status(order_id)
except OrderNotFound as e:
logger.exception(
f"Order {order_id} was not found in db "
f"to emit. Error: {e}."
)
raise
except ReceiptGenerationFailed as e:
logger.exception(
"Error found during emission! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise
except ReceiptEmissionFailed as e:
logger.exception(
"Emission failed! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise
else:
return {"order_id": order_id, "order_status": order_status.value}
Как вам? Намного лучше, правда? У нас есть один блок try
, который построен достаточно логично, чтобы понять, что произойдет дальше. Вы сгруппировали конкретные блоки, за исключением тех, которые помогают вам понять «когда» и крайние случаи. И, наконец, у вас есть блок else
, в котором описано, что произойдет, если все отработает как надо.
Кроме того, пожалуйста, обратите внимание на то, что я сохранил инструкции raise
без объявления объекта исключения. Это не опечатка. На самом деле, это правильный способ повторного вызова исключения: простой и немногословный.
Но это еще не все. Логирование продолжает меня раздражать.
Третье улучшение: улучшение логирования
Этот шаг напоминает мне принцип «говори, а не спрашивай», хотя это все же не совсем он. Вместо того, чтобы запрашивать подробности исключения и на их основе выдавать полезные сообщения, исключения должны выдавать их сами – в конце концов, я их конкретизировал!
### Exceptions
class OrderCreationException(Exception):
pass
class OrderNotFound(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id
super().__init__(
f"Order {order_id} was not found in db "
"to emit."
)
class ReceiptGenerationFailed(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id
super().__init__(
"Error found during emission! "
f"Order: {order_id}"
)
class ReceiptEmissionFailed(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id
super().__init__(
"Emission failed! "
f"Order: {order_id} "
)
### Main class
class OrderService:
def emit(self, order_id: str) -> dict:
try:
...
except OrderNotFound:
logger.exception("We got a database exception")
raise
except ReceiptGenerationFailed:
logger.exception("We got a problem generating the receipt")
raise
except ReceiptEmissionFailed:
logger.exception("Unable to emit the receipt")
raise
else:
return {"order_id": order_id, "order_status": order_status.value}
Наконец-то мои глаза чувствуют облегчение. Поменьше повторений, пожалуйста! Примите к сведению, что рекомендуемый способ выглядит так, как я написал его выше: logger.exception
(«ЛЮБОЕ СООБЩЕНИЕ»). Вам даже не нужно передавать исключение, поскольку его наличие уже подразумевается. Кроме того, кастомное сообщение, которое мы определили в каждом исключении с идентификатором order_id
, будет отображаться в логах, поэтому вам не нужно повторяться и не нужно оперировать внутренними данными об исключениях.
Вот пример вывода ваших логов:
❯ python3 testme.py
Unable to emit the receipt # <<-- My log message
Traceback (most recent call last):
File "/path/testme.py", line 19, in <module>
tryme()
File "/path/testme.py", line 14, in tryme
raise ReceiptEmissionFailed(order_id)
ReceiptEmissionFailed: Emission failed! Order: 10 # <<-- My exception message
Теперь всякий раз, когда я получаю это исключение, сообщение уже ясно и понятно, и мне не нужно помнить о логировании order_id
, который я сгенерировал.
Последнее улучшение: упрощение
После более детального рассмотрения нашего окончательного кода, он кажется лучше, теперь его легко читать и поддерживать.
Но управляет ли OrderService
бизнес-логикой? Я не думаю, что это сервис в общем смысле. Он больше похож на координацию вызовов настоящих сервисов обеспечивающих бизнес-логику, которая выглядит получше, чем паттерн facade.
Кроме того, можно заметить, что он запрашивает данные у status_service
, чтобы что-то с ними сделать. (Что, на этот раз, действительно разрушает идею «Говори, а не спрашивай»).
Перейдем к упрощению.
class OrderFacade: # Renamed to match what it actually is
def emit(self, order_id: str) -> dict:
try:
# NOTE: info logging still happens inside
status_service.ensure_order_unlocked(order_id)
receipt_note = receipt_service.create(order_id)
broker.emit_receipt_note(receipt_note)
order_status = status_service.get_order_status(order_id)
except OrderAlreadyInProgress as e:
# New block
logger.info("Aborting emission request because it is already in progress!")
return {"order_id": order_id, "order_status": e.order_status.value}
except OrderAlreadyEmitted as e:
# New block
logger.info(f"Aborting emission because it already happened! {e}")
return {"order_id": order_id, "order_status": e.order_status.value}
except OrderNotFound:
logger.exception("We got a database exception")
raise
except ReceiptGenerationFailed:
logger.exception("We got a problem generating the receipt")
raise
except ReceiptEmissionFailed:
logger.exception("Unable to emit the receipt")
raise
else:
return {"order_id": order_id, "order_status": order_status.value}
Мы только что создали новый метод ensure_order_unlocked
для нашего status_service
, который теперь отвечает за создание исключений/логирование в случае, если что-то идет не так.
Хорошо, а теперь скажите, насколько легче теперь стало это читать?
Я могу понять все return
при беглом просмотре. Я знаю, что происходит, когда все идет хорошо, и как крайние случаи могут привести к разным результатам. И все это без прокрутки взад-вперед.
Теперь этот код такой же простой, каким (в основном) должен быть любой код.
Обратите внимание, что я решил вывести объект исключения e в логах, поскольку под капотом он будет запускать str(e)
, который вернет сообщение об исключении. Я подумал, что было бы полезно говорить подробно, поскольку мы не используем log.exception
для этого блока, поэтому сообщение об исключении не будет отображаться.
Теперь давайте разберемся с некоторыми хитростями, которые помогут вам сделать код понятным для чтения и простым в обслуживании.
Эффективное создание исключений
Всегда классифицируйте свои исключения через базовое и расширяйте все конкретные исключения от него. С помощью этой полезной практики вы можете переиспользовать логику для связанного кода.
Исключения – это объекты, которые несут в себе информацию, поэтому не стесняйтесь добавлять кастомные атрибуты, которые могут помочь вам понять, что происходит. Не позволяйте своему бизнес-коду учить вас тому, как он должен быть построен, ведь с таким количеством сообщений и деталей потерять себя становится трудно.
# Base category exception
class OrderCreationException(Exception):
pass
# Specific error with custom message. Order id is required.
class OrderNotFound(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id # custom property
super().__init__(
f"Order {order_id} was not found in db "
f"to emit."
)
# Specific error with custom message. Order id is required.
class ReceiptGenerationFailed(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id # custom property
super().__init__(
"Error found during emission! "
f"Order: {order_id}"
)
В примере выше я мог бы выйти за рамки и расширить базовый класс, чтобы всегда получать order_id
, если мне это нужно. Этот совет поможет сохранить код сухим, поскольку мне не нужно быть многословным при создании исключений. Так можно использовать всего лишь одну переменную.
def func1(order_id):
raise OrderNotFound(order_id)
# instead of raise OrderNotFound(f"Can't find order {order_id}")
def func2(order_id):
raise OrderNotFound(order_id)
# instead of raise OrderNotFound(f"Can't find order {order_id}")
В тестировании также будет больше смысла, поскольку я могу сделать assert order_id
через строку.
assert e.order_id == order_id
# instead of assert order_id in str(e)
Ловим и создаем исключения эффективно
Еще одна вещь, которую люди часто делают неправильно – это отлавливают и повторно создают исключения.
Согласно PEP 3134 Python, делать нужно следующим образом.
Повторное создание исключения
Обычной инструкции raise
более чем достаточно.
try:
...
except CustomException as ex:
# do stuff (e.g. logging)
raise
Создание одного исключения из другого
Этот вариант особо актуален, поскольку он сохраняет всю трассировку стека и помогает вашей команде отлаживать основные проблемы.
try:
...
except CustomException as ex:
raise MyNewException() from ex
Эффективное логирование исключений
Еще один совет, который не позволит вам быть слишком многословным.
Используйте logger.exception
Вам не нужно логировать объект исключения. Функция exception
логгера предназначена для использования внутри блоков except
. Она уже обрабатывает трассировку стека с информацией о выполнении и отображает, какое исключение вызвало ее, с сообщением, установленном на уровне ошибки!
try:
...
except CustomException:
logger.exception("custom message")
А что, если это не ошибка?
Если по какой-то причине вы не хотите логировать исключение как ошибку, то возможно, это предупреждение или просто информация, как было показано выше.
Вы можете принять решение установить exc_info
в True
, если хотите сохранить трассировку стека. Кроме того, было бы неплохо использовать объект исключения внутри сообщения.
Источники
Документация Python:
· Python logging.logger.exception
· Python PEP 3134
Принципы и качество кода:
· Говори, а не спрашивай
· Паттерн facade
· Blob
— РЕГИСТРАЦИЯ
- Create a Custom Exception Class in Python
- Execute Exception-Handling Using the
try...except
Block in Python
This tutorial will demonstrate you can create custom exception classes in Python. Here, we’ll show how you can properly perform exception handling, define custom exception classes, and override existing built-in exceptions.
Exceptions are a type of event that occurs whenever something within a program doesn’t go as intended or disrupts the flow of the intended use-case of the program. Without exception handling, the program will cease to execute entirely, and the exception would have to either be fixed or handled.
Create a Custom Exception Class in Python
Creating an Exception Class in Python is done the same way as a regular class. The main difference is you have to include the Python’s base Exception
class to inform the compiler that the class you’re making is an exception class.
Let’s test this method out to create an exception class called DemoException
and use the placeholder control flow keyword pass
inside as a placeholder.
class DemoException(Exception):
pass
Execute Exception-Raising Using the Keyword raise
in Python
To test the DemoException
class and see what it displays when it’s actually triggered, perform exception raising. Exception-raising is synonymous with exception-throwing in other programming languages.
Using the keyword raise
, trigger an exception using the given exception class and outputs an exception message.
class DemoException(Exception):
pass
raise DemoException
Output:
Traceback (most recent call last):
File "/Users/demo/python/demo_exception.py", line 4, in <module>
raise DemoException
__main__.DemoException
A standard exception will look like in the terminal if no custom exception message has been declared.
Declare a Custom Exception Message in Python
To declare a custom exception message for DemoException
, override the __init__()
method of the exception class and include the message that should be outputted for the exception in the parameters, along with the mandatory self-referential parameter self
.
For example, let’s override the __init__()
method and create a custom message for the DemoException
class:
class DemoException(Exception):
def __init__(self, message):
super().__init__(message)
Take note that for the message to be integrated into your exception successfully, call the base Exception
class, __init__()
method, and include the message
as an argument.
Let’s call the exception class again using the raise
keyword, and now, passing a custom message with it:
class DemoException(Exception):
def __init__(self, message):
super().__init__(message)
message = "Exception Triggered! Something went wrong."
raise DemoException(message)
The output should look like this:
Traceback (most recent call last):
File "/Users/demo/python/helloworld.py", line 6, in <module>
raise DemoException(message)
__main__.DemoException: Exception Triggered! Something went wrong.
We’ve now successfully created and triggered an exception class with a custom error message.
For actual situations that may trigger an exception, how do we handle and raise these exceptions? You can solve this problem neatly by implementing exception-handling using the try...except
block.
Execute Exception-Handling Using the try...except
Block in Python
The try...except
block is much like the try-catch
block in other languages like Java.
The try...except
block has 2 main blocks and 2 optional blocks:
try
(required) — The main block responsible for encapsulating the code block where the exception might be triggered. Thetry
block halts the whole process within it whenever an exception is triggered.except
(required) — The block program proceeds whenever a specified exception is triggered. This block typically contains a descriptive error message for the caller or just a simpleprint()
statement. There may be more than oneexcept
block in a singletry
block, each one catching different exceptions.else
(optional) — This optional block is where the program will proceed if thetry
block did not trigger any exceptions.finally
(optional) — This optional block runs once everything from the previous 3 blocks has been performed regardless if an exception is triggered or not.
Let’s use the previous example using the DemoException
class to try a simple try...except
block.
First, wrap the raise
keyword in a function and put it inside the try...except
block.
The function that we’ll create for this example is a function that accepts a number and throws an exception if it sends 0
. If it sends any other number, then the code will proceed as intended. Check the example below:
class DemoException(Exception):
def __init__(self, message):
super().__init__(message)
message = "Exception Triggered! Something went wrong."
def triggerException(num):
if (num == 0):
raise DemoException(message)
else:
print(num)
try:
triggerException(0)
print("Code has successfully been executed.")
except DemoException:
print("Error: Number should not be 0.")
Since the triggerException()
passed 0
as an argument, the code should trigger DemoException
. Here we should expect the raise
keyword message to be overridden with whatever is inside the except
block as the output.
Notice that the print()
line after the triggerException()
function call was not outputted. It’s because the function raised an exception; therefore, it immediately halted all the processes within the try
block and proceeded directly to the except
block.
Output:
Error: Number should not be 0.
Now, let’s try passing a valid number like 20
, for example.
try:
triggerException(20)
print("Code has successfully been executed.")
except DemoException:
print("Error: Number should not be 0.")
Output:
20
Code has successfully been executed.
Let’s try chaining the except
blocks and create another exception. Let’s call the new exception NumberFormatException
, which triggers if the given input is not a number. For this exception class, let’s declare the message inside the class.
class NumberFormatException(Exception, value):
message = f'{value} is not a number'
def __init__(self):
super().__init__(message)
Now, modify the code above to handle the new exception class NumberFormatException
:
class DemoException(Exception):
def __init__(self, message):
super().__init__(message)
class NumberFormatException(Exception):
def __init__(self, message, value):
message = f'{value} is not a number'
super().__init__(message)
message = "Exception occured."
def triggerException(num):
if (not num.isdigit()):
raise NumberFormatException(message, num)
elif (num == 0):
raise DemoException(message)
else:
print(num)
num = "sample string"
try:
triggerException(num)
print("Code has successfully been executed.")
except DemoException:
print("Error: Number should not be 0.")
except NumberFormatException:
print(num+" is not a number.")
In this code, the value of num
that was passed to triggerException()
is a string 'sample string'
so the NumberFormatException
should be triggered.
Output:
sample string is not a number.
In summary, creating custom exceptions in Python is as simple as creating a new class, but with the Exception
class as an extra argument in the class definition. The raise
keyword is used to trigger exceptions given the Exception Class. The try...except
blocks are used to wrap one or more exceptions within a code block and modify what the code does when handling that exception and not just shutting down the program entirely.
Until now error messages haven’t been more than mentioned, but if you have tried
out the examples you have probably seen some. There are (at least) two
distinguishable kinds of errors: syntax errors and exceptions.
8.1. Syntax Errors¶
Syntax errors, also known as parsing errors, are perhaps the most common kind of
complaint you get while you are still learning Python:
>>> while True print('Hello world') File "<stdin>", line 1 while True print('Hello world') ^ SyntaxError: invalid syntax
The parser repeats the offending line and displays a little ‘arrow’ pointing at
the earliest point in the line where the error was detected. The error is
caused by (or at least detected at) the token preceding the arrow: in the
example, the error is detected at the function print()
, since a colon
(':'
) is missing before it. File name and line number are printed so you
know where to look in case the input came from a script.
8.2. Exceptions¶
Even if a statement or expression is syntactically correct, it may cause an
error when an attempt is made to execute it. Errors detected during execution
are called exceptions and are not unconditionally fatal: you will soon learn
how to handle them in Python programs. Most exceptions are not handled by
programs, however, and result in error messages as shown here:
>>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: division by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'spam' is not defined >>> '2' + 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't convert 'int' object to str implicitly
The last line of the error message indicates what happened. Exceptions come in
different types, and the type is printed as part of the message: the types in
the example are ZeroDivisionError
, NameError
and TypeError
.
The string printed as the exception type is the name of the built-in exception
that occurred. This is true for all built-in exceptions, but need not be true
for user-defined exceptions (although it is a useful convention). Standard
exception names are built-in identifiers (not reserved keywords).
The rest of the line provides detail based on the type of exception and what
caused it.
The preceding part of the error message shows the context where the exception
happened, in the form of a stack traceback. In general it contains a stack
traceback listing source lines; however, it will not display lines read from
standard input.
Built-in Exceptions lists the built-in exceptions and their meanings.
8.3. Handling Exceptions¶
It is possible to write programs that handle selected exceptions. Look at the
following example, which asks the user for input until a valid integer has been
entered, but allows the user to interrupt the program (using Control-C
or
whatever the operating system supports); note that a user-generated interruption
is signalled by raising the KeyboardInterrupt
exception.
>>> while True: ... try: ... x = int(input("Please enter a number: ")) ... break ... except ValueError: ... print("Oops! That was no valid number. Try again...") ...
The try
statement works as follows.
- First, the try clause (the statement(s) between the
try
and
except
keywords) is executed. - If no exception occurs, the except clause is skipped and execution of the
try
statement is finished. - If an exception occurs during execution of the try clause, the rest of the
clause is skipped. Then if its type matches the exception named after the
except
keyword, the except clause is executed, and then execution
continues after thetry
statement. - If an exception occurs which does not match the exception named in the except
clause, it is passed on to outertry
statements; if no handler is
found, it is an unhandled exception and execution stops with a message as
shown above.
A try
statement may have more than one except clause, to specify
handlers for different exceptions. At most one handler will be executed.
Handlers only handle exceptions that occur in the corresponding try clause, not
in other handlers of the same try
statement. An except clause may
name multiple exceptions as a parenthesized tuple, for example:
... except (RuntimeError, TypeError, NameError): ... pass
A class in an except
clause is compatible with an exception if it is
the same class or a base class thereof (but not the other way around — an
except clause listing a derived class is not compatible with a base class). For
example, the following code will print B, C, D in that order:
class B(Exception): pass class C(B): pass class D(C): pass for cls in [B, C, D]: try: raise cls() except D: print("D") except C: print("C") except B: print("B")
Note that if the except clauses were reversed (with except B
first), it
would have printed B, B, B — the first matching except clause is triggered.
The last except clause may omit the exception name(s), to serve as a wildcard.
Use this with extreme caution, since it is easy to mask a real programming error
in this way! It can also be used to print an error message and then re-raise
the exception (allowing a caller to handle the exception as well):
import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except OSError as err: print("OS error: {0}".format(err)) except ValueError: print("Could not convert data to an integer.") except: print("Unexpected error:", sys.exc_info()[0]) raise
The try
… except
statement has an optional else
clause, which, when present, must follow all except clauses. It is useful for
code that must be executed if the try clause does not raise an exception. For
example:
for arg in sys.argv[1:]: try: f = open(arg, 'r') except OSError: print('cannot open', arg) else: print(arg, 'has', len(f.readlines()), 'lines') f.close()
The use of the else
clause is better than adding additional code to
the try
clause because it avoids accidentally catching an exception
that wasn’t raised by the code being protected by the try
…
except
statement.
When an exception occurs, it may have an associated value, also known as the
exception’s argument. The presence and type of the argument depend on the
exception type.
The except clause may specify a variable after the exception name. The
variable is bound to an exception instance with the arguments stored in
instance.args
. For convenience, the exception instance defines
__str__()
so the arguments can be printed directly without having to
reference .args
. One may also instantiate an exception first before
raising it and add any attributes to it as desired.
>>> try: ... raise Exception('spam', 'eggs') ... except Exception as inst: ... print(type(inst)) # the exception instance ... print(inst.args) # arguments stored in .args ... print(inst) # __str__ allows args to be printed directly, ... # but may be overridden in exception subclasses ... x, y = inst.args # unpack args ... print('x =', x) ... print('y =', y) ... <class 'Exception'> ('spam', 'eggs') ('spam', 'eggs') x = spam y = eggs
If an exception has arguments, they are printed as the last part (‘detail’) of
the message for unhandled exceptions.
Exception handlers don’t just handle exceptions if they occur immediately in the
try clause, but also if they occur inside functions that are called (even
indirectly) in the try clause. For example:
>>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() ... except ZeroDivisionError as err: ... print('Handling run-time error:', err) ... Handling run-time error: division by zero
8.4. Raising Exceptions¶
The raise
statement allows the programmer to force a specified
exception to occur. For example:
>>> raise NameError('HiThere') Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: HiThere
The sole argument to raise
indicates the exception to be raised.
This must be either an exception instance or an exception class (a class that
derives from Exception
). If an exception class is passed, it will
be implicitly instantiated by calling its constructor with no arguments:
raise ValueError # shorthand for 'raise ValueError()'
If you need to determine whether an exception was raised but don’t intend to
handle it, a simpler form of the raise
statement allows you to
re-raise the exception:
>>> try: ... raise NameError('HiThere') ... except NameError: ... print('An exception flew by!') ... raise ... An exception flew by! Traceback (most recent call last): File "<stdin>", line 2, in <module> NameError: HiThere
8.5. User-defined Exceptions¶
Programs may name their own exceptions by creating a new exception class (see
Classes for more about Python classes). Exceptions should typically
be derived from the Exception
class, either directly or indirectly.
Exception classes can be defined which do anything any other class can do, but
are usually kept simple, often only offering a number of attributes that allow
information about the error to be extracted by handlers for the exception. When
creating a module that can raise several distinct errors, a common practice is
to create a base class for exceptions defined by that module, and subclass that
to create specific exception classes for different error conditions:
class Error(Exception): """Base class for exceptions in this module.""" pass class InputError(Error): """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__(self, expression, message): self.expression = expression self.message = message class TransitionError(Error): """Raised when an operation attempts a state transition that's not allowed. Attributes: previous -- state at beginning of transition next -- attempted new state message -- explanation of why the specific transition is not allowed """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
Most exceptions are defined with names that end in “Error,” similar to the
naming of the standard exceptions.
Many standard modules define their own exceptions to report errors that may
occur in functions they define. More information on classes is presented in
chapter Classes.
8.6. Defining Clean-up Actions¶
The try
statement has another optional clause which is intended to
define clean-up actions that must be executed under all circumstances. For
example:
>>> try: ... raise KeyboardInterrupt ... finally: ... print('Goodbye, world!') ... Goodbye, world! KeyboardInterrupt Traceback (most recent call last): File "<stdin>", line 2, in <module>
A finally clause is always executed before leaving the try
statement, whether an exception has occurred or not. When an exception has
occurred in the try
clause and has not been handled by an
except
clause (or it has occurred in an except
or
else
clause), it is re-raised after the finally
clause has
been executed. The finally
clause is also executed “on the way out”
when any other clause of the try
statement is left via a
break
, continue
or return
statement. A more
complicated example:
>>> def divide(x, y): ... try: ... result = x / y ... except ZeroDivisionError: ... print("division by zero!") ... else: ... print("result is", result) ... finally: ... print("executing finally clause") ... >>> divide(2, 1) result is 2.0 executing finally clause >>> divide(2, 0) division by zero! executing finally clause >>> divide("2", "1") executing finally clause Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in divide TypeError: unsupported operand type(s) for /: 'str' and 'str'
As you can see, the finally
clause is executed in any event. The
TypeError
raised by dividing two strings is not handled by the
except
clause and therefore re-raised after the finally
clause has been executed.
In real world applications, the finally
clause is useful for
releasing external resources (such as files or network connections), regardless
of whether the use of the resource was successful.
8.7. Predefined Clean-up Actions¶
Some objects define standard clean-up actions to be undertaken when the object
is no longer needed, regardless of whether or not the operation using the object
succeeded or failed. Look at the following example, which tries to open a file
and print its contents to the screen.
for line in open("myfile.txt"): print(line, end="")
The problem with this code is that it leaves the file open for an indeterminate
amount of time after this part of the code has finished executing.
This is not an issue in simple scripts, but can be a problem for larger
applications. The with
statement allows objects like files to be
used in a way that ensures they are always cleaned up promptly and correctly.
with open("myfile.txt") as f: for line in f: print(line, end="")
After the statement is executed, the file f is always closed, even if a
problem was encountered while processing the lines. Objects which, like files,
provide predefined clean-up actions will indicate this in their documentation.