See Exceptions
EAFP: easier to ask for forgiveness than permission (Perl programming language, per Larry Wall. See quotes of Larry Wall).
LBYL: look before you leap. Risk management philosophy at the heart of exceptions.
Try to anticipate things that can go wrong and mitigate the consequences. See https://stackoverflow.com/questions/11360858/what-is-the-eafp-principle-in-python. Note: good exam question.
Things can go wrong. Hope for the best, but expect the worst. In the words of the 20th century philosopher, Mike Tyson, everyone has a plan until they get punched in the face.
I spent years in finance as a risk manager. My job was to try to imagine things that could go wrong and take steps to mitigate, if not eliminate, those risks. Farmers sell their crops on future markets to hedge against price fluctuations. Airlines buy jet fuel on futures markets to hedge price risk. Multinational companies buy currency futures to hedge against foreign exchange risk. Farmers can also insure against bad weather, like drought. Homeowners buy insurance protecting them from fire, flood, theft, and vandalism. Some companies (but not many) had business interruption insurance that protected them against the covid-19 pandemic shutting down their business.
In each of the cases, there is a foreseeable, but uncertain, adverse event:
Investors can hedge their stock market investments using options. A call option pays off if the price of a stock goes up. A put option pays off if the price of a stock goes down. Many investors use combinations of options to protect their portfolios from uncertain future events.
In the words of another twentieth century philosopher, Yogi Berra, it is difficult to make predictions, especially about the future.
import this
The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
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
def f1():
return 1/0
f1()
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) /tmp/ipykernel_3588831/2719331641.py in <module> ----> 1 f1() /tmp/ipykernel_3588831/82817062.py in f1() 1 def f1(): ----> 2 return 1/0 ZeroDivisionError: division by zero
def f2():
return a
f2()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /tmp/ipykernel_3588831/1603317987.py in <module> ----> 1 f2() /tmp/ipykernel_3588831/1734449526.py in f2() 1 def f2(): ----> 2 return a NameError: name 'a' is not defined
def f3():
a = []
return a[2]
f3()
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) /tmp/ipykernel_3588831/1694196295.py in <module> ----> 1 f3() /tmp/ipykernel_3588831/1222251901.py in f3() 1 def f3(): 2 a = [] ----> 3 return a[2] IndexError: list index out of range
def f4():
return foo()
f4()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) /tmp/ipykernel_3588831/1205670228.py in <module> ----> 1 f4() /tmp/ipykernel_3588831/608990109.py in f4() 1 def f4(): ----> 2 return foo() NameError: name 'foo' is not defined
def f5():
for x in open("foo"):
print (x)
f5()
--------------------------------------------------------------------------- FileNotFoundError Traceback (most recent call last) /tmp/ipykernel_3588831/257402558.py in <module> ----> 1 f5() /tmp/ipykernel_3588831/3319180516.py in f5() 1 def f5(): ----> 2 for x in open("foo"): 3 print (x) FileNotFoundError: [Errno 2] No such file or directory: 'foo'
def f6():
import foo
f6()
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) /tmp/ipykernel_3588831/3151912711.py in <module> ----> 1 f6() /tmp/ipykernel_3588831/334674352.py in f6() 1 def f6(): ----> 2 import foo ModuleNotFoundError: No module named 'foo'
def f7():
d = {}
return d['key']
f7()
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) /tmp/ipykernel_3588831/24191879.py in <module> ----> 1 f7() /tmp/ipykernel_3588831/2890225209.py in f7() 1 def f7(): 2 d = {} ----> 3 return d['key'] KeyError: 'key'
import os
def f8():
os.mkdir("/hello")
f8()
--------------------------------------------------------------------------- PermissionError Traceback (most recent call last) /tmp/ipykernel_3588831/993499914.py in <module> ----> 1 f8() /tmp/ipykernel_3588831/2027675755.py in f8() 1 import os 2 def f8(): ----> 3 os.mkdir("/hello") PermissionError: [Errno 13] Permission denied: '/hello'
import sys
def f9():
# print (sys.getrecursionlimit()) ==> 1000
f9()
sys.getrecursionlimit()
3000
f9()
--------------------------------------------------------------------------- RecursionError Traceback (most recent call last) /tmp/ipykernel_3588831/1844206180.py in <module> ----> 1 f9() /tmp/ipykernel_3588831/380940650.py in f9() 2 def f9(): 3 # print (sys.getrecursionlimit()) ==> 1000 ----> 4 f9() ... last 1 frames repeated, from the frame below ... /tmp/ipykernel_3588831/380940650.py in f9() 2 def f9(): 3 # print (sys.getrecursionlimit()) ==> 1000 ----> 4 f9() RecursionError: maximum recursion depth exceeded
def f10():
g = range(5)
g.next()
f10()
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /tmp/ipykernel_3588831/1690477009.py in <module> ----> 1 f10() /tmp/ipykernel_3588831/1925680742.py in f10() 1 def f10(): 2 g = range(5) ----> 3 g.next() AttributeError: 'range' object has no attribute 'next'
def f11():
g = range(5)
next(g)
f11()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) /tmp/ipykernel_3588831/1809916670.py in <module> ----> 1 f11() /tmp/ipykernel_3588831/606302619.py in f11() 1 def f11(): 2 g = range(5) ----> 3 next(g) TypeError: 'range' object is not an iterator
g = range(10)
x = iter(g)
next(x)
0
def f12():
g = (x for x in range(1))
next(g)
next(g)
f12()
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) /tmp/ipykernel_3588831/3161046385.py in <module> ----> 1 f12() /tmp/ipykernel_3588831/3182897007.py in f12() 2 g = (x for x in range(1)) 3 next(g) ----> 4 next(g) StopIteration:
x = 0
def f13():
x += 1
return x
f13()
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) /tmp/ipykernel_3588831/1256359740.py in <module> ----> 1 f13() /tmp/ipykernel_3588831/871710112.py in f13() 1 x = 0 2 def f13(): ----> 3 x += 1 4 return x UnboundLocalError: local variable 'x' referenced before assignment
class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
# exceptions.MyError: 'oops!'
def f14():
raise MyError("oops!")
f14()
--------------------------------------------------------------------------- MyError Traceback (most recent call last) /tmp/ipykernel_3588831/529271966.py in <module> ----> 1 f14() /tmp/ipykernel_3588831/842390987.py in f14() 7 # exceptions.MyError: 'oops!' 8 def f14(): ----> 9 raise MyError("oops!") MyError: 'oops!'
def f14a():
raise Exception("oops!")
f14a()
--------------------------------------------------------------------------- Exception Traceback (most recent call last) /tmp/ipykernel_3588831/413542817.py in <module> ----> 1 f14a() /tmp/ipykernel_3588831/464713622.py in f14a() 1 def f14a(): ----> 2 raise Exception("oops!") Exception: oops!
def f15():
raise TypeError("this is a mistake")
f15()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-50-6f4b761b5f23> in <module> ----> 1 f15() <ipython-input-49-ee590fc88ca9> in f15() 1 def f15(): ----> 2 raise TypeError("this is a mistake") TypeError: this is a mistake
def f16():
try:
1 / 0
except:
print ("You raised an exception")
f16()
You raised an exception
def f17(x, y):
try:
result = x / y
except ZeroDivisionError:
print("division by zero!")
except TypeError:
print("Oops! wrong type!")
else:
print("result is", result)
finally:
print("executing finally clause")
f17(2,1)
result is 2.0 executing finally clause
f17(1,0)
division by zero! executing finally clause
f17('1','2')
Oops! wrong type! executing finally clause
f17(2,0)
division by zero! executing finally clause
f17(8,2)
result is 4.0 executing finally clause
like counttags() in homework
## import urllib
import urllib.request
def f17b(url):
try:
ufile = urllib.request.urlopen(url)
test = ufile.read()
return len(test)
except IOError:
return ('problem reading url:' + url)
f17b('http://www.cnn.com')
1112972
f17b('http://badcnn.com')
'problem reading url:http://badcnn.com'
f17b('http://cnnnn.com')
100
import sys
def f18():
try:
1/0
except:
e = sys.exc_info()[0]
print ( "Error: {}".format(e))
return sys.exc_info()
f18()
Error: <class 'ZeroDivisionError'>
(ZeroDivisionError, ZeroDivisionError('division by zero'), <traceback at 0x7fef7b6d5940>)
def f19(x):
if isinstance(x,float):
raise TypeError( "Can't work with float and can't convert to int, either" )
else:
return x
isinstance(9.0,float)
True
f19(1)
1
f19(1.0)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-76-c4ff23ae5d04> in <module> ----> 1 f19(1.0) <ipython-input-73-127036e72b77> in f19(x) 1 def f19(x): 2 if isinstance(x,float): ----> 3 raise TypeError( "Can't work with float and can't convert to int, either" ) 4 else: 5 return x TypeError: Can't work with float and can't convert to int, either
The assert()
function can be used to monitor the execution of the program. At any point, the programmer can assert statements that should be true. If the statement is not true, assert will raise an exception.
Below we assert that the input parameter, x, is even.
def f20(x):
assert(x % 2 == 0)
f20(2)
f20(3)
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-79-3a606a311c5b> in <module> ----> 1 f20(3) <ipython-input-77-40ae1ff3da8e> in f20(x) 1 def f20(x): ----> 2 assert(x % 2 == 0) AssertionError:
import errno
def f21(filename):
try:
f = open(filename)
except OSError as e:
print (e)
if e.errno == errno.ENOENT:
print ("File not found")
elif e.errno == errno.EACCES:
print ("Permission denied")
return e
else:
print ("No problem")
f21('filename')
[Errno 2] No such file or directory: 'filename' File not found
FileNotFoundError(2, 'No such file or directory')
f21("exceptions.py")
No problem
There are lots of possible errors.
dir(errno)
['E2BIG', 'EACCES', 'EADDRINUSE', 'EADDRNOTAVAIL', 'EADV', 'EAFNOSUPPORT', 'EAGAIN', 'EALREADY', 'EBADE', 'EBADF', 'EBADFD', 'EBADMSG', 'EBADR', 'EBADRQC', 'EBADSLT', 'EBFONT', 'EBUSY', 'ECANCELED', 'ECHILD', 'ECHRNG', 'ECOMM', 'ECONNABORTED', 'ECONNREFUSED', 'ECONNRESET', 'EDEADLK', 'EDEADLOCK', 'EDESTADDRREQ', 'EDOM', 'EDOTDOT', 'EDQUOT', 'EEXIST', 'EFAULT', 'EFBIG', 'EHOSTDOWN', 'EHOSTUNREACH', 'EIDRM', 'EILSEQ', 'EINPROGRESS', 'EINTR', 'EINVAL', 'EIO', 'EISCONN', 'EISDIR', 'EISNAM', 'EKEYEXPIRED', 'EKEYREJECTED', 'EKEYREVOKED', 'EL2HLT', 'EL2NSYNC', 'EL3HLT', 'EL3RST', 'ELIBACC', 'ELIBBAD', 'ELIBEXEC', 'ELIBMAX', 'ELIBSCN', 'ELNRNG', 'ELOOP', 'EMEDIUMTYPE', 'EMFILE', 'EMLINK', 'EMSGSIZE', 'EMULTIHOP', 'ENAMETOOLONG', 'ENAVAIL', 'ENETDOWN', 'ENETRESET', 'ENETUNREACH', 'ENFILE', 'ENOANO', 'ENOBUFS', 'ENOCSI', 'ENODATA', 'ENODEV', 'ENOENT', 'ENOEXEC', 'ENOKEY', 'ENOLCK', 'ENOLINK', 'ENOMEDIUM', 'ENOMEM', 'ENOMSG', 'ENONET', 'ENOPKG', 'ENOPROTOOPT', 'ENOSPC', 'ENOSR', 'ENOSTR', 'ENOSYS', 'ENOTBLK', 'ENOTCONN', 'ENOTDIR', 'ENOTEMPTY', 'ENOTNAM', 'ENOTRECOVERABLE', 'ENOTSOCK', 'ENOTSUP', 'ENOTTY', 'ENOTUNIQ', 'ENXIO', 'EOPNOTSUPP', 'EOVERFLOW', 'EOWNERDEAD', 'EPERM', 'EPFNOSUPPORT', 'EPIPE', 'EPROTO', 'EPROTONOSUPPORT', 'EPROTOTYPE', 'ERANGE', 'EREMCHG', 'EREMOTE', 'EREMOTEIO', 'ERESTART', 'ERFKILL', 'EROFS', 'ESHUTDOWN', 'ESOCKTNOSUPPORT', 'ESPIPE', 'ESRCH', 'ESRMNT', 'ESTALE', 'ESTRPIPE', 'ETIME', 'ETIMEDOUT', 'ETOOMANYREFS', 'ETXTBSY', 'EUCLEAN', 'EUNATCH', 'EUSERS', 'EWOULDBLOCK', 'EXDEV', 'EXFULL', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'errorcode']
x = f21('zzz')
[Errno 2] No such file or directory: 'zzz' File not found
x
FileNotFoundError(2, 'No such file or directory')
dir(x)
['__cause__', '__class__', '__context__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__suppress_context__', '__traceback__', 'args', 'characters_written', 'errno', 'filename', 'filename2', 'strerror', 'with_traceback']
x.filename
'zzz'
x.errno
2
The following three functions each handles a TypeError exception. See https://medium.com/swlh/handling-exceptions-in-python-a-cleaner-way-using-decorators-fae22aa0abec
def area_square(length):
try:
print(length**2)
except TypeError:
print("area_square only takes numbers as the argument")
def area_circle(radius):
try:
print(3.142 * radius**2)
except TypeError:
print("area_circle only takes numbers as the argument")
def area_rectangle(length, breadth):
try:
print(length * breadth)
except TypeError:
print("area_rectangle only takes numbers as the argument")
area_square(5)
area_square([5])
25 area_square only takes numbers as the argument
area_circle(5)
area_circle('five')
78.55 area_circle only takes numbers as the argument
area_rectangle(4,5)
area_rectangle('four','five')
20 area_rectangle only takes numbers as the argument
We can define a decorator and apply it to each function.
def exception_handler(func):
def inner_function(*args, **kwargs):
try:
func(*args, **kwargs)
except TypeError:
print(f"{func.__name__} only takes numbers as the argument")
return inner_function
@exception_handler
def area_square(length):
print(length * length)
@exception_handler
def area_circle(radius):
print(3.14 * radius * radius)
@exception_handler
def area_rectangle(length, breadth):
print(length * breadth)
area_square(2)
area_circle(2)
area_rectangle(2, 4)
area_square("some_str")
area_circle("some_other_str")
area_rectangle("some_other_rectangle")
4 12.56 8 area_square only takes numbers as the argument area_circle only takes numbers as the argument area_rectangle only takes numbers as the argument
See https://www.blog.pythonlibrary.org/2016/06/09/python-how-to-create-an-exception-logging-decorator/
The logging module provides a uniform process for logging code events. See https://docs.python.org/3/howto/logging.html
Here is an example.
import logging
def create_logger():
"""
Creates a logging object and returns it
"""
logger = logging.getLogger("example_logger")
logger.setLevel(logging.INFO)
# create the logging file handler
fh = logging.FileHandler("/tmp/test.log1")
fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
# add handler to logger object
logger.addHandler(fh)
return logger
logger = create_logger()
dir(logger)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cache', '_log', 'addFilter', 'addHandler', 'callHandlers', 'critical', 'debug', 'disabled', 'error', 'exception', 'fatal', 'filter', 'filters', 'findCaller', 'getChild', 'getEffectiveLevel', 'handle', 'handlers', 'hasHandlers', 'info', 'isEnabledFor', 'level', 'log', 'makeRecord', 'manager', 'name', 'parent', 'propagate', 'removeFilter', 'removeHandler', 'root', 'setLevel', 'warn', 'warning']
logger.info('This is info')
logger.warning('this is a warning')
logger.exception('this is an exception')
import subprocess
(status, output) = subprocess.getstatusoutput('cat /tmp/test.log1')
print (output)
2020-10-21 10:57:21,331 - example_logger - INFO - This is info 2020-10-21 10:57:21,331 - example_logger - WARNING - this is a warning 2020-10-21 10:57:21,331 - example_logger - ERROR - this is an exception NoneType: None
import functools
def exception(function):
"""
A decorator that wraps the passed in function and logs
exceptions should one occur
"""
@functools.wraps(function)
def wrapper(*args, **kwargs):
logger = create_logger()
try:
return function(*args, **kwargs)
except:
# log the exception
err = "There was an exception in "
err += function.__name__
logger.exception(err)
# re-raise the exception
raise
return wrapper
@exception
def zero_divide():
1 / 0
zero_divide()
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-55-cdacd07e3c56> in <module> ----> 1 zero_divide() <ipython-input-53-1ee4c85e0261> in wrapper(*args, **kwargs) 10 logger = create_logger() 11 try: ---> 12 return function(*args, **kwargs) 13 except: 14 # log the exception <ipython-input-54-72d12a448a54> in zero_divide() 1 @exception 2 def zero_divide(): ----> 3 1 / 0 ZeroDivisionError: division by zero
(status, output) = subprocess.getstatusoutput('cat /tmp/test.log1')
print (output)
2020-10-21 10:57:21,331 - example_logger - INFO - This is info 2020-10-21 10:57:21,331 - example_logger - WARNING - this is a warning 2020-10-21 10:57:21,331 - example_logger - ERROR - this is an exception NoneType: None 2020-10-21 10:59:53,456 - example_logger - ERROR - There was an exception in zero_divide Traceback (most recent call last): File "<ipython-input-53-1ee4c85e0261>", line 12, in wrapper return function(*args, **kwargs) File "<ipython-input-54-72d12a448a54>", line 3, in zero_divide 1 / 0 ZeroDivisionError: division by zero 2020-10-21 10:59:53,456 - example_logger - ERROR - There was an exception in zero_divide Traceback (most recent call last): File "<ipython-input-53-1ee4c85e0261>", line 12, in wrapper return function(*args, **kwargs) File "<ipython-input-54-72d12a448a54>", line 3, in zero_divide 1 / 0 ZeroDivisionError: division by zero
End of Exceptions notebook