NB: Exception Handling

Overview

All happy families are alike, but every unhappy family is unhappy in its own way.
– Leo Tolstoy, Anna Karenina, 1878.

There are two ways that programs can succeed of fail:

They can meet or not meet the motiving requirements for the code.

  • Does it do what is is supposed to do?

  • Does it add value to a process?

  • Is it ethical? Does it do harm?

They can just work or not work:

  • Does the run at all?

  • Is the code brittle or buggy?

  • Is it sensitive to contextual factors like bad user input or a bad database connection?

  • Does the code work but produce incorrect outcomes?

These lead to two practices:

  • Strategic: Project management

  • Tactical: Testing and debugging

Today, we will focus on some concepts and tools for the latter.

  • We want to know whether our code is working or not.

  • We want to know if our code is of a high quality.

Approaches to quality

  • Through good design

  • Throigh principled testing

Some design principles

  • Favor the simple over the complex

Don’t confuse the easy with the simple!

  • Invest in data design to simplify algorithm design

  • Write literate code

  • Given a choice, use tools that are documented and have a user community

  • Many other things :-)

Some testing activities

  • Syntax checking

  • Debugging

  • Ad hoc testing

  • Unit-testing

  • User testing

Some concepts

  • Beware of silent failures

  • Testing for the absence of bugs is impossible

    Like Popper’s concept of falsifiability: We can never prove a scientific hypothesis to be true, we can only falsify.

Exception Handling

Programming languages offer many tools for testing.

These include mechanisms to define and capture errors in code.

We call these errors exceptions.

  • An exception is an event that occurs during the execution of a program that disrupts the normal flow of the program’s instructions.

We call we call dealing with these errors exception handling.

  • When a Python script encounters a code failure, it raises an exception.

Exception handling is the foundation of unit testing.

Python’s Tools

Exceptions

  • A Python object that represents an error

  • Objects that classify kinds of errors

  • Coders can define these

Exception Handlers

  • Control structures and functions to deal with exceptions

  • try/except

  • raise

  • assert

Unit Testing Frameworks

  • Robot

  • PyTest

  • Unittest

  • DocTest

  • Nose2

  • Testify

Errors and Exceptions

A common technical interview question: Explain the difference between an error and an exception.

An error is a serious problem that a reasonable application should not try to catch.
An error will stop execution. It means the code if broken and needs to be rewritten.

An exception is an issue that is expected or known to occur (e.g., division by zero).
Software must handle exceptions. For example, one bad row of data shouldn’t bring down the application.

Some Common Built-in Exceptions

Python comes with a number of predefined exceptions.

Libraries introduce new ones, too.

They used in raise statement by the authors of programs when a possible error is anticipated.

Here are some common examples.

ZeroDivisionError

3 / 0
ZeroDivisionError: division by zero

Syntax Error

## if-statement missing colon at end

if x > 0
  print("uh oh")
SyntaxError: expected ':' (223904042.py, line 3)

NameError

## references an undefined variable

print(x)
NameError: name 'x' is not defined

IndexError

## loop goes off the end of the list

lst = [0, 1, 2]

for i in range(4):
    print(lst[i])
0
1
2
IndexError: list index out of range

Exception Handling with try/except

We use try/except blocks to handle exceptions in our code.

These blocks work as follows: * The try block will contain a statement. * If the statement fails, the the flow goes to the except block. * The exception block will have code to handle the error, rather than halting the program.

The process is very similar to if/then: * If there is an error, then raise an excpetion.

Multiple except statements may be given, to handle specific exceptions.

Below, we give a catch-all except for any kind of exception.

An excpetion is a mistake that will halt the execution of code. * For example, trying to access a dictionary key that does not exit. * Or, a database connection fails. * Very often, execeptions are caused by an external dependency failing, such as another system or the user.

Let’s try referencing a variable that doesn’t exist with no exception handling.

print(a)
NameError: name 'a' is not defined

Now let’s try referencing a variable that doesn’t exist with a try/except block to handle the exception.

try:
    print(a)
except:
   print("caught an exception")
caught an exception

Assert Statements

Another to catch errors before they happen is to test if computations turned out as expected.

We use assert to verify an expression is True.
- if expression is True, nothing happens. - if expression is False, Python raises an AssertionError exception.

Assert statements have the followinf syntax:

assert Expression[, Arguments]

where [, Arguments] denotes optional arguments.

For example, let’s say a program is expecting three arguments to be passed from the command line.

The variable num_args counts the arguments.

num_args = 3

assert num_args == 3, "number of arguments must be 3!"

The assert evaluates to True, and things proceed normally without exception.

If we change num_args = 4 this will throw an AssertionError with the provided message.

The program then stops.

num_args = 4

assert num_args == 3, "number of arguments must be 3!"
AssertionError: number of arguments must be 3!

If the assert is not given a message, it throws AssertionError:

num_args = 4

assert num_args == 3
AssertionError: 
try:
    assert num_args == 3
except:
    print("Got an error")
Got an error
try:
    assert num_args == 3, "Number of arguments must be 3!"
except AssertionError as e:
    print(e)
Number of arguments must be 3!

raise()

Exceptions can be raised, too.

Use this if you want to halt the program and let other code that is using this code handle it.|

try:
    10/0
except:
    raise ZeroDivisionError("Hey, you can't divide by zero!")
ZeroDivisionError: Hey, you can't divide by zero!
raise IndexError("Bad Index")
IndexError: Bad Index

Futher Reading

  • https://www.geeksforgeeks.org/errors-and-exceptions-in-python/
  • https://www.tutorialspoint.com/python/assertions_in_python.htm