Exceptions ========== .. toctree:: :maxdepth: 1 Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` There are many situations where the normal flow of your program needs to be interrupted to deal with an unusual or exceptional condition. Quite often, you can't predict when (in time) or where (in your code) these exceptional conditions will occur. For example: * The user presses :kbd:`CTRL-C` on the keyboard * The disk you are trying to write to becomes full * Your program attempted to divide something by 0. Python pre-defines a lot of exceptions, which can be found `here `_. When one of these exceptional conditions is detected, Python raises an exception at the point at which it detects the problem. It is also possible for you to purposely raise exceptions from within your code. Exceptions are not automatically fatal as Python provides syntax to trap for and handle them. .. _section_heading-The_Try_Statement: The try Statement ----------------- The :py:keyword:`try` statement is used to setup exception handling around a group of statements that you want to protect. It looks generally like the following [1]_: .. code-block:: python try: try_suite except exception_group1 as variable1: except_suite1 ... except exception_groupN as variableN: except_suiteN else: else_suite finally: finally_suite Let's break down the various parts: The ``try`` and ``try_suite`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Required:** Yes .. code-block:: python :emphasize-lines: 1-2 try: try_suite except exception_group1 as variable1: except_suite1 ... except exception_groupN as variableN: except_suiteN else: else_suite finally: finally_suite * This is where the protected statements are placed. If an exception occurs while this suite of code is being executed, the rest of the statements in the suite are skipped and Python searches for an exception handler to handle it. The ``except``, ``variable`` and ``except_suite`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Required:** ``except`` and ``except_suite`` -> Yes (1 or more); ``variable`` -> no .. code-block:: python :emphasize-lines: 3-7 try: try_suite except exception_group1 as variable1: except_suite1 ... except exception_groupN as variableN: except_suiteN else: else_suite finally: finally_suite * This is where you specify the class name of the exception(s) to trap for and the handling code to execute for each. Can be: * A single exception, or, * A tuple of exceptions, or, * Empty, which traps for all exceptions (for obvious reasons, be careful to put this as the last except clause). For example: .. code-block:: python except KeyboardInterrupt as ex: pass except (OSError, EOFError) as ex: pass except: # Put empty 'except' clause last # as it traps for everything else pass * When an exception is thrown, the :py:keyword:`except` statements are searched in-turn until a matching one is found. * Exceptions that are thrown will match an :py:keyword:`except` clause if the exception given in the :py:keyword:`except` clause is the same class as, or **base** class of, the exception that was thrown. * If the ``as`` keyword is specified, the exception object is made available to the handler by the name :py:keyword:`except`. * Once an :py:keyword:`except` clause is matched, the search is terminated. If no matching :py:keyword:`except` clause is found, the search continues in the surrounding code blocks. .. note:: If no exception handler is found by the time the top of the program is reached, this is considered an unhandled exception and the program is terminated. When the program is terminated this way, a stack trace is printed to the screen to assist with debug. This is often viewed as "dirty" by users of your program because it is not only cryptic and difficult for most people to read and interpret, but, it also exposes a hole in your software you didn't protect against. * If the exception is handled, program execution continues into the :py:keyword:`finally` block (if present) and then off the end of the :py:keyword:`try` statement. * If the exception handler causes another exception, execution of the ``except_suite`` is stopped, the original exception is nested inside the new exception via the new exception's ``__context__`` attribute and a search is started fo find a handler for the new exception. .. tip:: Generally speaking, don't trap for exceptions unless you intend to do something about it. Trapping it but doing nothing is useless. Trapping it and re-throwing it is useless. The ``else`` and ``else_suite`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Required:** No .. code-block:: python :emphasize-lines: 8-9 try: try_suite except exception_group1 as variable1: except_suite1 ... except exception_groupN as variableN: except_suiteN else: else_suite finally: finally_suite * If present, the ``else_suite`` is executed if **no** exception occurs. Otherwise, it is skipped. * If an exception occurs in the ``else`` clause, the preceding :py:keyword:`except` clauses are not searched again. The exception must be handled by an outer exception trap. .. tip:: Use an :py:keyword:`else` clause when you want code to not be protected by the handlers defined in the current :py:keyword:`try` statement (i.e. you want the code protected by a handler created outside the :py:keyword:`try` statement.). The ``finally`` and ``finally_suite`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Required:** No .. code-block:: python :emphasize-lines: 10-11 try: try_suite except exception_group1 as variable1: except_suite1 ... except exception_groupN as variableN: except_suiteN else: else_suite finally: finally_suite * Is **always** executed, whether an exception happens or not. If there is no exception handler in the try structure to handle the current exception, the finally block is still executed and the exception subsequently re-raised. .. tip:: Use a finally block to allow the program to do things like clean up after itself (for example, release file handles and close network sockets gracefully). * If you use a :py:keyword:`break`, :py:keyword:`continue` or :py:keyword:`return` statement in a try suite, the finally clause is still executed. A return statement is therefore useless in the try suite if a return value is also present in the finally suite because the value returned is always from the last return statement executed. For example: >>> def foo(): ... try: ... return 'Hi' ... except Exception as ex: ... pass ... finally: ... return "Bye" >>> foo() 'Bye' * If the ``finally_suite`` causes another exception, execution of the ``finally_suite`` is stopped, the original exception is nested inside the new exception via the new exception's ``__context__`` attribute and a search is started fo find a handler for the new exception. .. _section_heading-Control_Flow_Examples: Control Flow Examples ^^^^^^^^^^^^^^^^^^^^^ The diagrams in :numref:`figure-normal_control_flow`, :numref:`figure-handled_exception_control_flow` and :numref:`figure-unhandled_exception_control_flow` illustrate the various control flow patterns that the :py:keyword:`try` statement supports. .. _figure-normal_control_flow: .. figure:: normal_control_flow.png :scale: 100 % :align: center Normal Control Flow .. _figure-handled_exception_control_flow: .. figure:: handled_exception_control_flow.png :scale: 100 % :align: center Handled Exception Control Flow .. _figure-unhandled_exception_control_flow: .. figure:: unhandled_exception_control_flow.png :scale: 100 % :align: center Unhandled Exception Control Flow .. _section_heading-Exception_Arguments: Exception Arguments ------------------- Exceptions have "associated values" (a.k.a. the "exception arguments") which frequently contains information about the cause of the exception. Most exceptions take at least one, optional, input object (like a number or a string as a message). Exactly what information is provided is exception dependent. The `description `_ of the built-in exception classes talks about what extra information is available in them. If the interpreter creates the exception, it populates the associated values. If your program purposely raises an exception, you can set those values when you call the exception objects constructor to create it. If you try and print an exception object, the exceptions associated values is typically what is printed. For example the following code: .. code-block:: python :emphasize-lines: 6 fh = None try: fh = open('asdf.txt') except OSError as ex: print("Oh no...something went wrong. Here's what it is:") print(ex) finally: if fh is not None: fh.close() Returns the following when printing the exception object: .. code-block:: text :emphasize-lines: 2 Oh no...something went wrong. Here's what it is: [Errno 2] No such file or directory: 'asdf.txt' .. _section_heading-Raising_Exceptions: Raising Exceptions ------------------ Your program can throw an exception by executing the :py:keyword:`raise` statement, which has the following general syntax: .. py:function:: raise exception(*args) Raises the given ``exception``, which can be either an exception class name, or, an instance of an exception class. For example: .. code-block:: python :emphasize-lines: 4 def func(): print("FUNC: Doing some stuff...") print("FUNC: *** Raising an exception ***") raise AttributeError("Do you have the right duck?") print("FUNC: More stuff...") try: print("TRY : Executing try...") func() except AttributeError as ex: print("EXC : Got an exception. Handling it.") print("EXC : {}".format(type(ex))) print("EXC : {}".format(ex)) finally: print("FIN : Done try") Produces the following output: .. code-block:: text :emphasize-lines: 5-6 TRY : Executing try... FUNC: Doing some stuff... FUNC: *** Raising an exception *** EXC : Got an exception. Handling it. EXC : EXC : Do you have the right duck? FIN : Done try If you truly want to just re-raise an exception that you trapped, you can just type :py:keyword:`raise`. .. code-block:: python :emphasize-lines: 4 def func(): print("FUNC: Doing some stuff...") print("FUNC: *** Raising an exception ***") raise AttributeError("Do you have the right duck?") print("FUNC: More stuff...") try: print("OTRY: Executing outer try...") # NOTE: Nested try block try: print("ITRY: Executing inner try...") func() except Exception as ex: print("IEXC: Got an exception, but, can't handle it.") print("IEXC: Re-raising...") raise finally: print("IFIN: Done inner try") except AttributeError as ex: print("OEXC: Got an exception. Handling it.") print("OEXC: {}".format(type(ex))) print("OEXC: {}".format(ex)) finally: print("OFIN: Done outer try") Produces the following output: .. code-block:: text :emphasize-lines: 9-10 OTRY: Executing outer try... ITRY: Executing inner try... FUNC: Doing some stuff... FUNC: *** Raising an exception *** IEXC: Got an exception, but, can't handle it. IEXC: Re-raising... IFIN: Done inner try OEXC: Got an exception. Handling it. OEXC: OEXC: Do you have the right duck? OFIN: Done outer try .. _section_heading-Custom_Exception_Classes: Custom Exception Classes ------------------------ Exceptions are objects and new object classes are created by deriving from a base class. In Python, new exceptions are created by deriving from the Exception class, or, one of its sub-classes. The hierarchy of built-in exceptions is found `here `_. .. tip:: When creating your own exception class the name should usually end in 'Error' (unless of course it actually doesn't represent an error). For example a new exception can be defined as simply as: >>> class MyException(Exception): pass And then you can use it in your program: .. code-block:: python :emphasize-lines: 4 def func(): print("FUNC: Doing some stuff...") print("FUNC: *** Raising an exception ***") raise MyException("So do you like, stuff?") print("FUNC: More stuff...") try: print("TRY : Executing try...") func() except MyException as ex: print("EXC : Got an exception. Handling it.") print("EXC : {}".format(type(ex))) print("EXC : {}".format(ex)) finally: print("FIN : Done try") .. code-block:: text :emphasize-lines: 5-6 TRY : Executing try... FUNC: Doing some stuff... FUNC: *** Raising an exception *** EXC : Got an exception. Handling it. EXC : EXC : So do you like, stuff? FIN : Done try Exception classes can do what any other class can do (execute specific code on construction, define additional properties etc). However, that will be covered in the section on Classes. .. _section_heading-Performance_Advantages: Performance Advantages ---------------------- A common coding style in Python embraces the notion that it is easier to ask for forgiveness than permission. The :py:keyword:`try` statement supports this style. Consider for example, the two approaches to opening a file: * **Ask for permission**: Check if the file exists and that you have permissions to read it prior to trying to open the file. Implies use of the :py:keyword:`if` statement. * **Beg for forgiveness**: Try to open the file, if there is an exception generated from it being a non-existent file or insufficient user permissions, handle it gracefully. Implies use of the :py:keyword:`try` statement. We can test the performance difference between these two styles using the code contained in this :download:`notebook ` which declares two life form classes, and asks them to make their characteristic sound **if they can**. .. code-block:: python :linenos: class duck(object): # Can make a sound def make_sound(self): print("QUACK!") class fish(object): # Can't make a sound pass def ask_permission(obj): if hasattr(obj, "make_sound"): obj.make_sound() else: print("Life form makes no sound.") def beg_forgiveness(obj): try: obj.make_sound() except AttributeError: print("Life form makes no sound.") The performance results from running this code 1000x, and averagaed over multple runs, are found :download:`here ` and summarized in :numref:`table-Exception_Testing_Results`: .. _table-Exception_Testing_Results: .. table:: Table of Exception Testing Results +-----------------------+--------------------+--------------------+ | Style | Makes Sound (Duck) | Sound-less (Fish) | +=======================+====================+====================+ | Ask Permission (IF) | 0.002834s | 0.003065s | +-----------------------+--------------------+--------------------+ | Beg Forgiveness (TRY) | 0.002676s | 0.003300s | +-----------------------+--------------------+--------------------+ | Difference | -0.0001575s | 0.0002355s | +-----------------------+--------------------+--------------------+ | Winner | BEG (IF) | ASK (TRY) | +-----------------------+--------------------+--------------------+ The performance take-aways from this table are the following: * The "IF" version always costs you. The more costly the check (for example checking a network drive vs checking a local drive), the greater the performance penalty compared to the "TRY" version. * The "TRY" version only costs you if an exception is actually thrown (and then it can be quite costly). So, if the chance of an exception is high, then use the "IF" version. But if the exception is infrequent (i.e. truly exceptional), use the "TRY" version. From a code construction point of view (applies to all applications) the "TRY" version is generally favored overall because: * The "IF" version has you wind up with a lot of "if error" in your code which can affect readability and maintainability. * The "TRY" version cleanly separates the business logic from the error logic. General suggestions: * By default wrap your code with TRY statements to improve readability and maintainability. * If you have performance issues, investigate why and selectively convert to the "IF" based structure as warranted. .. [1] Syntax representation re-used from, Programming in Python3, Mark Summerfield, 2009, Addison-Wesley. .. _concrete_exceptions : https://docs.python.org/3.5/library/exceptions.html#concrete-exceptions .. _exception_hierarchy : https://docs.python.org/3.5/library/exceptions.html#exception-hierarchy