Python3 Errors and Exceptions
As a beginner in Python, you often encounter error messages when you start learning Python programming, which we haven't mentioned before. In this section, we will introduce them specifically.
Python has two easily recognizable types of errors: syntax errors and exceptions.
Python's assert
(assertion) is used to evaluate an expression, and it triggers an exception when the condition of the expression is false.
Syntax Errors
Syntax errors in Python, or parsing errors, are commonly encountered by beginners, as shown in the following example:
>>> while True print('Hello world')
File "<stdin>", line 1, in ?
while True print('Hello world')
^
SyntaxError: invalid syntax
In this example, the function print()
is found to have an error; it is missing a colon :
before it.
The syntax analyzer points out the line where the error occurred and marks the position of the first found error with a small arrow.
Exceptions
Even if the syntax of a Python program is correct, errors can occur when running it. Errors detected during runtime are called exceptions.
Most exceptions are not handled by the program and are displayed as error messages:
Example
>>> 10 * (1/0) # 0 cannot be used as a divisor, triggers an exception
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero
>>> 4 + spam*3 # spam is undefined, triggers an exception
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2 # int cannot be added to str, triggers an exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
Exceptions appear in different types, which are printed as part of the message: types in the example include ZeroDivisionError
, NameError
, and TypeError
.
The initial part of the error message shows the context in which the exception occurred, displayed in the form of a call stack.
Exception Handling
try/except
Exceptions can be caught using the try/except
statement.
In the following example, the user is prompted to enter a valid integer, but the user can interrupt the program (using Control-C or a method provided by the operating system). The interruption information triggers a KeyboardInterrupt
exception.
while True:
try:
x = int(input("Please enter a number: "))
break
except ValueError:
print("That was not a valid number. Try again!")
The try
statement works as follows:
- First, the try clause (the statements between the
try
andexcept
keywords) is executed. - If no exception occurs, the except clause is ignored, and execution of the
try
clause ends. - If an exception occurs during the execution of the try clause, the rest of the try clause is skipped. If the type of the exception matches the name after
except
, the correspondingexcept
clause is executed. - If an exception does not match any
except
clause, it is passed on to the outertry
statement.
A try
statement may have multiple except
clauses to handle different specific exceptions. Only one branch will be executed.
The handler will only handle exceptions that occur in the corresponding try
clause, not in other try
handlers.
An except
clause can handle multiple exceptions, which are placed in a tuple, for example:
except (RuntimeError, TypeError, NameError):
pass
The last except
clause can omit the exception name, serving as a wildcard. You can use this method to print an error message and then re-raise the exception.
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
try/except...else
The try/except
statement also has an optional else clause, which, if used, must be placed after all the except clauses.
The else clause will be executed if the try clause does not raise any exceptions.
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
>>> try:
raise MyError(2*2)
except MyError as e:
print('My exception occurred, value:', e.value)
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'
In this example, the default __init__() of the Exception class is overridden.
When creating a module that may raise multiple different exceptions, a common practice is to establish a base exception class for the package and then create different subclasses for different error situations:
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 exception names end with "Error," similar to the naming of standard exceptions.
Defining Cleanup Actions
The try statement has another optional clause that defines cleanup actions that will be executed under any circumstances. For example:
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyboardInterrupt
In the above example, the finally clause will execute regardless of whether an exception occurs in the try clause.
If an exception is raised in the try clause (or in the except and else clauses) and is not caught by any except clause, it will be raised again after the finally clause executes.
Here is a more complex example (with both except and finally clauses in the same try statement):
>>> 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 ?
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
Predefined Clean-up Actions
Some objects define standard clean-up actions, which are performed whether the system successfully used the object or not. Once the object is no longer needed, the standard clean-up action is executed.
The following example demonstrates opening a file and printing its contents to the screen:
for line in open("myfile.txt"):
print(line, end="")
The issue with the above code is that the file remains open after execution and is not closed.
The with
statement ensures that objects like files are properly cleaned up after use:
with open("myfile.txt") as f:
for line in f:
print(line, end="")
After the above code executes, the file f
will always be closed, even if errors occur during processing.
For more information on the with
keyword, refer to Python with Keyword.