5.5. Exceptions¶
5.5.1. Rationale¶
Used when error occurs
You can catch exception and handles erroneous situation
Exception example situations:
File does not exists
No permissions to read file
Function argument is invalid type (ie.
int('one')
)Value is incorrect (ie. negative Kelvin temperature)
Network or database connection could not be established
5.5.2. Most Common Exceptions¶
AttributeError
- Attribute reference or assignment fails:
>>> name = 'Jan'
>>> name.append('Twardowski')
Traceback (most recent call last):
AttributeError: 'str' object has no attribute 'append'
ImportError
, ModuleNotFoundError
- Module could not be located:
>>> import math
>>> import match
Traceback (most recent call last):
ModuleNotFoundError: No module named 'match'
IndexError
- Sequence subscript is out of range:
>>> DATA = ['a', 'b', 'c']
>>> DATA[100]
Traceback (most recent call last):
IndexError: list index out of range
KeyError
- Dictionary key is not found:
>>> DATA = {'a': 1, 'b': 2}
>>> DATA['x']
Traceback (most recent call last):
KeyError: 'x'
NameError
- Local or global name is not found:
>>> print(firstname)
Traceback (most recent call last):
NameError: name 'firstname' is not defined
SyntaxError
- Parser encounters a syntax error:
>>> if True
... print('Yes')
Traceback (most recent call last):
SyntaxError: invalid syntax
IndentationError
- Syntax errors related to incorrect indentation:
>>> if True:
... print('Hello!')
... print('My name...')
... print('José Jiménez')
Traceback (most recent call last):
IndentationError: unexpected indent
TypeError
- Operation or function is applied to an object of inappropriate type:
>>> 42 + 'a'
Traceback (most recent call last):
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> 'a' + 42
Traceback (most recent call last):
TypeError: can only concatenate str (not "int") to str
>>> a = ['a', 'b', 'c']
>>> a[1.5]
Traceback (most recent call last):
TypeError: list indices must be integers or slices, not float
>>> a, b = 1
Traceback (most recent call last):
TypeError: cannot unpack non-iterable int object
ValueError
Argument has an invalid value:
>>> a, b, c = 1, 2
Traceback (most recent call last):
ValueError: not enough values to unpack (expected 3, got 2)
>>> a, b = 1, 2, 3
Traceback (most recent call last):
ValueError: too many values to unpack (expected 2)
>>> float('one')
Traceback (most recent call last):
ValueError: could not convert string to float: 'one'
>>> int('one')
Traceback (most recent call last):
ValueError: invalid literal for int() with base 10: 'one'
5.5.3. Exception Hierarchy¶
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
+-- 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
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
5.5.4. Raising Exceptions¶
Raise Exception without message:
>>> raise RuntimeError
Traceback (most recent call last):
RuntimeError
Exception with additional message:
>>> raise RuntimeError('Some message')
Traceback (most recent call last):
RuntimeError: Some message
5.5.5. Use Case¶
>>> input = lambda _: -10 # Assume user will input -10
>>>
>>> temperature = input('Type temperature [Kelvin]: ')
>>>
>>> if float(temperature) < 0:
... raise ValueError('Kelvin temperature cannot be negative')
Traceback (most recent call last):
ValueError: Kelvin temperature cannot be negative
>>> def convert(temperature):
... if type(temperature) not in {float, int}:
... raise TypeError('Temperature must be int or float')
... if temperature < 0:
... raise ValueError('Kelvin temperature cannot be negative')
... return temperature
>>> def apollo13():
... raise RuntimeError('Oxygen tank explosion')
>>>
>>>
>>> apollo13()
Traceback (most recent call last):
RuntimeError: Oxygen tank explosion
>>> def apollo18():
... raise NotImplementedError('Mission dropped due to budget cuts')
>>>
>>>
>>> apollo18()
Traceback (most recent call last):
NotImplementedError: Mission dropped due to budget cuts
5.5.6. Assertion¶
Raises
AssertionError
if argument isFalse
Can have optional message
>>> data = [1, 2, 3] >>> assert type(data) is list >>> assert all(type(x) is int for x in data)
>>> data = ('a', 'b', 'c') >>> assert type(data) is list Traceback (most recent call last): AssertionError >>> assert all(type(x) is int for x in data) Traceback (most recent call last): AssertionError
>>> import sys >>> assert sys.version_info >= (3, 9) >>> assert sys.version_info >= (3, 9), 'Python 3.9+ required'
5.5.7. Traceback Analysis¶
Stacktrace is 8 levels deep, it's not Java's 200 ;)
>>> raise RuntimeError Traceback (most recent call last): File "<stdin>", line 1, in <module> RuntimeError
>>> raise RuntimeError('Huston we have a problem') Traceback (most recent call last): File "<stdin>", line 1, in <module> RuntimeError: Huston we have a problem
>>> def apollo13(): ... raise RuntimeError('Oxygen tank explosion') >>> >>> >>> apollo13() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in apollo13 RuntimeError: Oxygen tank explosion
>>> def apollo13(): ... raise RuntimeError('Oxygen tank explosion') >>> >>> >>> apollo13() Traceback (most recent call last): File "/home/watney/myscript.py", line 4, in <module> apollo13() File "/home/watney/myscript.py", line 2, in apollo13 raise RuntimeError('Oxygen tank explosion') RuntimeError: Oxygen tank explosion
>>> def apollo13(): ... raise RuntimeError('Oxygen tank explosion') >>> >>> >>> apollo13() Traceback (most recent call last): File "<input>", line 1, in <module> File "/Applications/PyCharm 2019.2 EAP.app/Contents/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile pydev_imports.execfile(filename, global_vars, local_vars) # execute the script File "/Applications/PyCharm 2019.2 EAP.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "/home/watney/myscript.py", line 4, in <module> apollo13() File "/home/watney/myscript.py", line 2, in apollo13 raise RuntimeError('Oxygen tank explosion') RuntimeError: Oxygen tank explosion
5.5.8. Change Verbosity Level¶
Change level with
sys.tracebacklimit
From time to time you can have problems somewhere in the middle, but it's rare
Last lines are the most important, in most cases error is there
>>> import sys >>> sys.tracebacklimit = 2 >>> >>> >>> def apollo13(): ... raise RuntimeError('Oxygen tank explosion') >>> >>> apollo13() Traceback (most recent call last): File "/home/watney/myscript.py", line 4, in <module> apollo13() File "/home/watney/myscript.py", line 2, in apollo13 raise RuntimeError('Oxygen tank explosion') RuntimeError: Oxygen tank explosion
5.5.9. Catching Exceptions¶
try
except
else
finally
try
is required and then one of the others blocks>>> try: ... # try to execute ... pass ... except Exception: ... # what to do if exception occurs ... pass ... else: ... # what to do if no exception occurs ... pass ... finally: ... # What to do either if exception occurs or not ... pass
Catch single exception:
>>> def apollo13():
... raise RuntimeError('Oxygen tank explosion')
>>>
>>>
>>> try:
... apollo13()
... except RuntimeError:
... print('Houston we have a problem')
Houston we have a problem
Catch many exceptions with the same handling:
>>> def apollo13():
... raise RuntimeError('Oxygen tank explosion')
>>>
>>>
>>> try:
... apollo13()
... except (RuntimeError, TypeError, NameError):
... print('Houston we have a problem')
Houston we have a problem
Catch many exceptions with different handling:
>>> try:
... with open(r'/tmp/my-file.txt') as file:
... print(file.read())
... except FileNotFoundError:
... print('File does not exist')
... except PermissionError:
... print('Permission denied')
File does not exist
Exceptions logging:
>>> import logging
>>>
>>>
>>> def apollo13():
... raise RuntimeError('Oxygen tank explosion')
>>>
>>> try:
... apollo13()
... except RuntimeError as err:
... logging.error(err)
... #stderr: ERROR:root:Oxygen tank explosion
5.5.10. Else and Finally¶
else
is executed when no exception occurredfinally
is executed always (even if there was exception)Used to close file, connection or transaction to database
>>> try: ... file = open('/tmp/myfile.txt') ... except Exception: ... print('Error, file cannot be open') ... else: ... file.close() Error, file cannot be open
else
is executed when no exception occurred:
>>> def apollo11():
... print('Try landing on the Moon')
>>>
>>> try:
... apollo11()
... except Exception:
... print('Abort')
... else:
... print('Landing a man on the Moon')
Try landing on the Moon
Landing a man on the Moon
finally
is executed always (even if there was exception):
>>> def apollo11():
... print('Try landing on the Moon')
>>>
>>> try:
... apollo11()
... except Exception:
... print('Abort')
... finally:
... print('Returning safely to the Earth')
Try landing on the Moon
Returning safely to the Earth
>>> def apollo11():
... print('Program P63 - Landing Manoeuvre Approach Phase')
... raise RuntimeError('1201 Alarm')
... raise RuntimeError('1202 Alarm')
... print('Contact lights')
... print('The Eagle has landed!')
... print("That's one small step for [a] man, one giant leap for mankind.")
>>>
>>> try:
... apollo11()
... except RuntimeError:
... print("You're GO for landing")
... except Exception:
... print('Abort')
... else:
... print('Landing a man on the Moon')
... finally:
... print('Returning safely to the Earth')
Program P63 - Landing Manoeuvre Approach Phase
You're GO for landing
Returning safely to the Earth
5.5.11. Pokemon Exception Handling¶
"Gotta catch 'em all"
Ctrl-C
raisesKeyboardInterrupt
User cannot simply kill program with Ctrl-C
:
>>> # doctest: +SKIP
... while True:
... try:
... number = float(input('Type number: '))
... except:
... continue
User can kill program with Ctrl-C
:
>>> # doctest: +SKIP
... while True:
... try:
... number = float(input('Type number: '))
... except Exception:
... continue
5.5.12. Defining Own Exceptions¶
class which inherits from
Exception
>>> class MyError(Exception): ... pass >>> >>> >>> raise MyError Traceback (most recent call last): MyError >>> >>> raise MyError('More verbose description') Traceback (most recent call last): MyError: More verbose description
Django Framework Use-case of Custom Exceptions:
>>> # doctest: +SKIP
... from django.contrib.auth.models import User
>>>
>>>
>>> def login(request):
... username = request.POST.get('username')
... password = request.POST.get('password')
...
... try:
... user = User.objects.get(username, password)
... except User.DoesNotExist:
... print('Sorry, no such user in database')
Django Framework Use-case of Custom Exceptions:
>>> class Dragon:
... def take_damage(self, damage):
... raise self.IsDead
...
... class IsDead(Exception):
... pass
>>>
>>>
>>> wawelski = Dragon()
>>>
>>> try:
... wawelski.take_damage(10)
... except Dragon.IsDead:
... print('Dragon is dead')
Dragon is dead
5.5.13. Exit Status Code¶
exit status
0
- no errorany other exit status - error
This will not work in Jupyter
>>> try: ... float('hello') ... except ValueError: ... print('Cannot type cast to float') ... exit(1) Traceback (most recent call last): SystemExit: 1
$ python3.9 -m doctest myscript.py
$ echo $?
0
$ python3.9 -m doctest myscript.py
**********************************************************************
File "/home/watney/myscript.py", line 41, in myscript
Failed example:
1 + 2
Expected:
3
Got:
4
**********************************************************************
1 items had failures:
1 of 2 in myscript
***Test Failed*** 1 failures.
$ echo $?
1
5.5.14. Assignments¶
"""
* Assignment: Exception Assert
* Complexity: easy
* Lines of code: 1 lines
* Time: 3 min
English:
1. Use data from "Given" section (see below)
2. Use `assert` keyword
3. Check if current Python version is greater or equal to `required`
4. If not, raise exception with message 'Python 3.7+ required'
Polish:
1. Użyj danych z sekcji "Given" (patrz poniżej)
2. Użyj słowa kluczowego `assert`
3. Sprawdź czy obecna wersja Python jest większa lub równa `required`
4. Jeżeli nie, podnieś wyjątek z komunikatem 'Python 3.7+ required'
Tests:
>>> type(sys)
<class 'module'>
>>> type(current_version)
<class 'sys.version_info'>
>>> type(required)
<class 'tuple'>
>>> required
(3, 7)
"""
# Given
import sys
current_version = sys.version_info
required = (3, 7)
"""
* Assignment: Exception Raise
* Complexity: easy
* Lines of code: 2 lines
* Time: 3 min
English:
1. Ask user to input temperature in Kelvins
2. User will always type proper `int` or `float`
3. Raise `ValueError` exception if temperature is less than 0
Polish:
1. Poproś użytkownika o wprowadzenie temperatury w Kelwinach
2. Użytkownik zawsze poda poprawne `int` lub `float`
3. Podnieś wyjątek `ValueError` jeżeli temperatura jest poniżej 0
Tests:
TODO: Doctests
TODO: Input Stub
>>> type(temperature)
<class 'float'>
>>> import sys
>>> sys.modules[__name__]
"""
# Given
# temperature = input('Type temperature: ')
temperature = 10
temperature = float(temperature)
"""
* Assignment: Exception Except
* Complexity: easy
* Lines of code: 6 lines
* Time: 3 min
English:
1. Ask user to input temperature in Kelvins
2. Try converting temperature to `float`
3. If unsuccessful, then print 'Invalid temperature' and exit with status code 1
Polish:
1. Poproś użytkownika o wprowadzenie temperatury w Kelwinach
2. Spróbuj skonwertować temperaturę do `float`
3. Jeżeli się nie uda to wypisz 'Invalid temperature' i wyjdź z kodem błędu 1
Tests:
TODO: Doctests
TODO: Input Stub
"""
# Given
temperature = input('Type temperature: ')
"""
* Assignment: Exception Finally
* Complexity: easy
* Lines of code: 2 lines
* Time: 2 min
English:
1. Ask user to input age
2. If user has less than 18 years
3. Raise an exception `PermissionError` with message "Adults only"
Polish:
1. Poproś użytkownika o wprowadzenie wieku
2. Jeżeli użytkownik ma mniej niż 18 lat
3. Wyrzuć wyjątek `PermissionError` z komunikatem "Adults only"
Tests:
TODO: Doctests
TODO: Input Stub
"""
# Given
ADULT = 18
age = input('Type age: ')
"""
* Assignment: Exception Else
* Complexity: easy
* Lines of code: 2 lines
* Time: 2 min
English:
1. Ask user to input age
2. If user has less than 18 years
3. Raise an exception `PermissionError` with message "Adults only"
Polish:
1. Poproś użytkownika o wprowadzenie wieku
2. Jeżeli użytkownik ma mniej niż 18 lat
3. Wyrzuć wyjątek `PermissionError` z komunikatem "Adults only"
Tests:
TODO: Doctests
TODO: Input Stub
"""
# Given
ADULT = 18
age = input('Type age: ')
"""
* Assignment: Exception Custom
* Complexity: easy
* Lines of code: 4 lines
* Time: 3 min
English:
1. Ask user to input angle in degrees
2. Define custom exception `NegativeKelvin`
3. If input temperature is lower than 0, raise `NegativeKelvin`
Polish:
1. Poproś użytkownika o wprowadzenie kąta
2. Zdefiniuj własny wyjątek `NegativeKelvin`
3. Jeżeli wprowadzona temperature jest mniejsza niż 0, podnieś `NegativeKelvin`
Tests:
>>> type(temperature)
<class 'float'>
>>> from inspect import isclass
>>> isclass(NegativeKelvin)
True
>>> issubclass(NegativeKelvin, Exception)
True
TODO: Input Stub
"""
# Given
temperature = input('Type temperature: ')
temperature = float(temperature)
"""
* Assignment: Exception Input
* Complexity: easy
* Lines of code: 9 lines
* Time: 5 min
English:
1. Ask user to input angle in degrees
2. User can input any value from keyboard (even nonnumeric)
2. Cotangent for 180 degrees is infinite
3. Define own exception `CotangentError`
4. If user typed angle equal to 180, raise your exception
Polish:
1. Poproś użytkownika o wprowadzenie kąta
2. Uwaga, użytkownik może podać dowolną wartość z klawiatury (nawet nienumeryczną)
2. Cotangens dla konta 180 ma nieskończoną wartość
3. Zdefiniuj własny wyjątek `CotangentError`
4. Jeżeli użytkownik wprowadził kąt równy 180, podnieś swój wyjątek
Tests:
TODO: Doctests
TODO: Input Stub
"""
# Given
degrees = input('Type angle [deg]: ')