It’s how we deal with them that matters. Same in Python: understanding and handling errors is the key to good code.
Python will always try to tell you what it thinks went wrong: “I didn’t understand what you meant by this…” or “I’m sorry, I can’t let you do that Dave…”
The challenges are:
That the ‘error’ isn’t always the error…
This outputs:
In the same way that variables have types, so do errors:
We can add our own messages:
We can create our own types (classes) of error:
This can then be triggered with:
And (very importantly) this can be caught with:
This means that exceptions could accept custom arguments, perform tidying-up or rollback operations, etc.
Python calls errors exceptions, so this leads to:
try:
#... some code that might fail...
except <Named_Error_Type>:
#... what do it if it fails for a specific reason...
except:
#... what to do if it fails for any other reason...
finally:
#... always do this, even if it fails...
You can use any or all of these together: you can have multiple named except
s to handle different types of errors from a single block of code; you do not have to have a catch-all except
or a finally
.
This code fails:
And it generates this error:
But if you ‘trap’ the error using except
then:
x,y = 10,0
try:
print(x/y)
except ZeroDivisionError:
print("You can't divide by zero!")
except:
print("Something has gone very wrong.")
finally:
print("Division is fun!")
This will print
> You can't divide by zero!
> Division is fun!
You can trigger your own exceptions using raise
.
x,y = 10,0
try:
print(x/y)
except ZeroDivisionError:
print("You can't divide by zero!")
raise Exception("Please don't do that again!")
finally:
print("Division is fun!")
try
triggers the ZeroDivisionError
block."You can't divide by zero!"
raise
a new exception that is not caught.finally
code executes because it always does before Python exits.Thus: ‘During handling of above (ZeroDivisionError) another exception (our Exception) occurred…’
We can actually think of exceptions as a way to develop our code.
Here’s some ‘pseudo-code’:
# Testing the 'addition' operator
test(1+1, 2) # Should equal 2
test(1+'1', TypeError) # Should equal TypeError
test('1'+'1', '11') # Should equal '11'
test(-1+1, 0) # Should equal 0
Our test(A,B)
function takes an input (A) and the expected output (B) and then compares them. The test returns True
if A==B and False
otherwise.
Each test
is a Unit Test because it tests one thing and one thing only. So if you had three functions to ‘do stuff’ then you’d need at least three unit tests.
A Unit Test may be composed of one or more assertions. Our pseudo-code on the previous slide contained 4 assertions.
A Unit Test does not mean that your code is correct or will perform properly under all circumstances. It means that your code returns the expected value for a specified input.
Python considers this approach so important that it’s built in.
This is an explict assertion to test fun
:
import unittest
def fun(x):
return x + 1
class MyTest(unittest.TestCase):
def test(self):
self.assertEqual(fun(3), 4)
print("Assertion 1 passed.")
self.assertEqual(fun(3), 5)
print("Assertion 2 passed.")
m = MyTest()
m.test()
The critical output is:
This approach uses the ‘docstring’ (the bits between """
) to test the results of the function. This is intended to encourage good documentation of functions using examples:
The Unit Test approach is often used on collaborative projects, especially in the Open Source world. PySAL, for instance, asks for unit tests with every new feature or integration.
The running of all tests for multiple components is called ‘integration testing’.
A commit, merge, or pull on GitHub can trigger the unit testing process for the entire software ‘stack’. This is known as Continuous Integration because you are always checking that the code works as expected, rather than leaving testing to the end.
Errors • Jon Reades