Exception handling
This chapter will discuss different types of errors and how to handle some of the them within the program gracefully. You'll also see how to raise exceptions programmatically.
Syntax errors
Quoting from docs.python: Errors and Exceptions:
There are (at least) two distinguishable kinds of errors: syntax errors and exceptions
Here's an example program with syntax errors:
# syntax_error.py
print('hello')
def main():
num = 5
total = num + 09
print(total)
main)
The above code is using an unsupported syntax for a numerical value. Note that the syntax check happens before any code is executed, which is why you don't see the output for the print('hello')
statement. Can you spot the rest of the syntax issues in the above program?
$ python3.9 syntax_error.py
File "/home/learnbyexample/Python/programs/syntax_error.py", line 5
total = num + 09
^
SyntaxError: leading zeros in decimal integer literals are not permitted;
use an 0o prefix for octal integers
try-except
Exceptions happen when something goes wrong during the code execution. For example, passing a wrong data type to a function, dividing a number by 0
and so on. Such errors are typically difficult or impossible to determine just by looking at the code.
>>> int('42')
42
>>> int('42x')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '42x'
>>> 3.14 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: float division by zero
When an exception occurs, the program stops executing and displays the line that caused the error. You also get an error type, such as ValueError
and ZeroDivisionError
seen in the above example, followed by a message. This may differ for user defined error types.
You could implement alternatives to be followed for certain types of errors instead of premature end to the program execution. For example, you could allow the user to correct their input data. In some cases, you want the program to end, but display a user friendly message instead of developer friendly traceback.
Put the code likely to generate an exception inside try
block and provide alternate path(s) inside one or more except
blocks. Here's an example to get a positive integer number from the user, and continue doing so if the input was invalid.
# try_except.py
from math import factorial
while True:
try:
num = int(input('Enter a positive integer: '))
print(f'{num}! = {factorial(num)}')
break
except ValueError:
print('Not a positive integer, try again')
It so happens that both int()
and factorial()
generate ValueError
in the above example. If you wish to take the same alternate path for multiple errors, you can pass a tuple
to except
instead of a single error type. Here's a sample run:
$ python3.9 try_except.py
Enter a positive integer: 3.14
Not a positive integer, try again
Enter a positive integer: hi
Not a positive integer, try again
Enter a positive integer: -2
Not a positive integer, try again
Enter a positive integer: 5
5! = 120
You can also capture the error message using the as
keyword (which you have seen previously with import
statement, and will come up again in later chapters). Here's an example:
>>> try:
... num = 5 / 0
... except ZeroDivisionError as e:
... print(f'oops something went wrong! the error msg is:\n"{e}"')
...
oops something went wrong! the error msg is:
"division by zero"
See docs.python: built-in exceptions for documentation on built-in exceptions.
It is not recommended to use
except
without passing an error type. See stackoverflow: avoid bare exceptions for details.
There are static code analysis tools like pylint, "which looks for programming errors, helps enforcing a coding standard, sniffs for code smells and offers simple refactoring suggestions". See awesome-python: code-analysis for more such tools.
else
The else
clause behaves similarly to the else
clause seen with loops. If there's no exception raised in the try
block, then the code in the else
block will be executed. This block should be defined after the except
block(s). As per the documentation:
The use of the
else
clause is better than adding additional code to thetry
clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by thetry ... except
statement.
# try_except_else.py
while True:
try:
num = int(input('Enter an integer number: '))
except ValueError:
print('Not an integer, try again')
else:
print(f'Square of {num} is {num ** 2}')
break
Here's a sample run:
$ python3.9 try_except_else.py
Enter an integer number: hi
Not an integer, try again
Enter an integer number: 3.14
Not an integer, try again
Enter an integer number: 42x
Not an integer, try again
Enter an integer number: -2
Square of -2 is 4
raise
You can also manually raise
exceptions if needed. It accepts an optional error type, which can be either a built-in or a user defined one (see docs.python: User-defined Exceptions). And you can optionally specify an error message. raise
by itself re-raises the currently active exception, if any (RuntimeError
otherwise).
>>> def sum2nums(n1, n2):
... types_allowed = (int, float)
... if type(n1) not in types_allowed or type(n2) not in types_allowed:
... raise TypeError('Argument should be an integer or a float value')
... return n1 + n2
...
>>> sum2nums(3.14, -2)
1.1400000000000001
>>> sum2nums(3.14, 'a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in sum2nums
TypeError: Argument should be an integer or a float value
finally
You can add code in finally
block that should always be the last thing done by the try
statement, irrespective of whether an exception has occurred. This should be declared after except
and the optional else
blocks.
# try_except_finally.py
try:
num = int(input('Enter a positive integer: '))
if num < 0:
raise ValueError
except ValueError:
print('Not a positive integer, run the program again')
else:
print(f'Square root of {num} is {num ** 0.5:.3f}')
finally:
print('\nThanks for using the program, have a nice day')
Here's some sample runs when the user enters some value:
$ python3.9 try_except_finally.py
Enter a positive integer: -2
Not a positive integer, run the program again
Thanks for using the program, have a nice day
$ python3.9 try_except_finally.py
Enter a positive integer: 2
Square root of 2 is 1.414
Thanks for using the program, have a nice day
Here's an example where something goes wrong, but not handled by the try
statement. Note that finally
block is still executed.
# here, user presses Ctrl+D instead of entering a value
# you'll get KeyboardInterrupt if the user presses Ctrl+C
$ python3.9 try_except_finally.py
Enter a positive integer:
Thanks for using the program, have a nice day
Traceback (most recent call last):
File "/home/learnbyexample/Python/programs/try_except_finally.py",
line 2, in <module>
num = int(input('Enter a positive integer: '))
EOFError
See docs.python: Defining Clean-up Actions for details like what happens if an exception occurs within an else
clause, presence of break/continue/return
etc. The documentation also gives examples of where finally
is typically used.
Exercises
Identify the syntax errors in the following code snippets. Try to spot them manually.
# snippet 1: def greeting() print('hello') # snippet 2: num = 5 if num = 4: print('what is going on?!') # snippet 3: greeting = “hi”
In case you didn't complete the exercises from Importing your own module section, you should be able to do it now.
Write a function
num(ip)
that accepts a single argument and returns the corresponding integer or floating-point number contained in the argument. Onlyint
,float
andstr
should be accepted as valid input data type. Provide custom error message if the input cannot be converted to a valid number. Examples are shown below:>>> num(0x1f) 31 >>> num(3.32) 3.32 >>> num(' \t 52 \t') 52 >>> num('3.982e5') 398200.0 # wrong data type >>> num(['1', '2.3']) TypeError: not a valid input # string input that cannot be converted to a valid int/float number >>> num('foo') ValueError: could not convert string to int or float