# Python Programming/Exceptions

Python 2 handles all errors with exceptions.

An *exception* is a signal that an error or other unusual condition has
occurred. There are a number of built-in exceptions, which indicate
conditions like reading past the end of a file, or dividing by zero. You
can also define your own exceptions.

### Overview

Exceptions in Python at a glance:

``` Python
import random
try:
  ri = random.randint(0, 2)
  if ri == 0:
    infinity = 1/0
  elif ri == 1:
    raise ValueError("Message")
    #raise ValueError, "Message" # Deprecated
  elif ri == 2:
    raise ValueError # Without message
except ZeroDivisionError:
  pass
except ValueError as valerr:
# except ValueError, valerr: # Deprecated?
  print(valerr)
  raise # Raises the exception just caught
except: # Any other exception
  pass
finally: # Optional
  pass # Clean up

class CustomValueError(ValueError): pass # Custom exception
try:
  raise CustomValueError
  raise TypeError
except (ValueError, TypeError): # Value error catches custom, a derived class, as well
  pass                          # A tuple catches multiple exception classes
```

### Raising exceptions

Whenever your program attempts to do something erroneous or meaningless,
Python raises exception to such conduct:

``` python
>>> 1 / 0
Traceback (most recent call last):
    File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero
```

This *traceback* indicates that the `ZeroDivisionError` exception is
being raised. This is a built-in exception \-- see below for a list of
all the other ones.

### Catching exceptions

In order to handle errors, you can set up *exception handling blocks* in
your code. The keywords `try` and `except` are used to catch exceptions.
When an error occurs within the `try` block, Python looks for a matching
`except` block to handle it. If there is one, execution jumps there.

If you execute this code:

``` python
try:
    print(1/0)
except ZeroDivisionError:
    print("You can't divide by zero!")
```

Then Python will print this:

You can\'t divide by zero!

If you don\'t specify an exception type on the `except` line, it will
cheerfully catch all exceptions. This is generally a bad idea in
production code, since it means your program will blissfully ignore
*unexpected* errors as well as ones which the `except` block is actually
prepared to handle.

Exceptions can propagate up the call stack:

``` python
def f(x):
    return g(x) + 1

def g(x):
    if x < 0: raise ValueError, "I can't cope with a negative number here."
    else: return 5

try:
    print(f(-6))
except ValueError:
    print("That value was invalid.")
```

In this code, the `print` statement calls the function `f`. That
function calls the function `g`, which will raise an exception of type
ValueError. Neither `f` nor `g` has a `try`/`except` block to handle
ValueError. So the exception raised propagates out to the main code,
where there *is* an exception-handling block waiting for it. This code
prints:

That value was invalid.

Sometimes it is useful to find out exactly what went wrong, or to print
the python error text yourself. For example:

``` python
try:
    the_file = open("the_parrot")
except IOError, (ErrorNumber, ErrorMessage):
    if ErrorNumber == 2: # file not found
        print("Sorry, 'the_parrot' has apparently joined the choir invisible.")
    else:
        print("Congratulation! you have managed to trip a #%d error" % ErrorNumber)
        print(ErrorMessage)
```

Which will print:

Sorry, \'the_parrot\' has apparently joined the choir invisible.

#### Custom Exceptions

Code similar to that seen above can be used to create custom exceptions
and pass information along with them. This can be very useful when
trying to debug complicated projects. Here is how that code would look;
first creating the custom exception class:

``` python
class CustomException(Exception):
    def __init__(self, value):
        self.parameter = value
    def __str__(self):
        return repr(self.parameter)
```

And then using that exception:

``` python
try:
    raise CustomException("My Useful Error Message")
except CustomException, (instance):
    print("Caught: " + instance.parameter)
```

#### Trying over and over again

### Recovering and continuing with `finally`

Exceptions could lead to a situation where, after raising an exception,
the code block where the exception occurred might not be revisited. In
some cases this might leave external resources used by the program in an
unknown state.

`finally` clause allows programmers to close such resources in case of
an exception. Between 2.4 and 2.5 version of python there is change of
syntax for `finally` clause.

-   Python 2.4

``` python
try:
    result = None
    try:
        result = x/y
    except ZeroDivisionError:
        print("division by zero!")
    print("result is ", result)
finally:
    print("executing finally clause")
```

-   Python 2.5

``` python
try:
    result = x / y
except ZeroDivisionError:
    print("division by zero!")
else:
    print("result is", result)
finally:
    print("executing finally clause")
```

### Built-in exception classes

All built-in Python
exceptions

### Exotic uses of exceptions

Exceptions are good for more than just error handling. If you have a
complicated piece of code to choose which of several courses of action
to take, it can be useful to use exceptions to jump out of the code as
soon as the decision can be made. The Python-based mailing list software
Mailman does this in deciding how a message should be handled. Using
exceptions like this may seem like it\'s a sort of GOTO \-- and indeed
it is, but a limited one called an *escape continuation*. Continuations
are a powerful functional-programming tool and it can be useful to learn
them.

Just as a simple example of how exceptions make programming easier, say
you want to add items to a list but you don\'t want to use \"if\"
statements to initialize the list we could replace this:

``` python
if hasattr(self, 'items'):
    self.items.extend(new_items)
else:
    self.items = list(new_items)
```

Using exceptions, we can emphasize the normal program flow---that
usually we just extend the list---rather than emphasizing the unusual
case:

``` python
try:
    self.items.extend(new_items)
except AttributeError:
    self.items = list(new_items)
```

### External links

-   8. Errors and
    Exceptions in The
    Python Tutorial, docs.python.org
-   8. Errors and
    Exceptions in The
    Python Tutorial for Python 2.4, docs.python.org
-   6. Built-in
    Exceptions,
    docs.python.org

