Python Exception Class Hierarchy

Python Exception Handling – UnboundLocalError

Making our way through our in-depth Python Exception Handling series, today we’ll be getting into the UnboundLocalError. An UnboundLocalError is raised when a local variable is referenced before it has been assigned. This error is a subclass of the Python NameError we explored in another recent article.

Throughout the remainder of this post we’ll examine the UnboundLocalError in more detail, starting with where it sits in the larger Python Exception Class Hierarchy. We’ll also look at some functional sample code showing the slight difference between NameErrors and UnboundLocalErrors, and how you can avoid UnboundLocalErrors with special statements found in Python like global. Let the games begin!

The Technical Rundown

All Python exceptions inherit from the BaseException class, or extend from an inherited class therein. The full exception hierarchy of this error is:

Full Code Sample

Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.

When Should You Use It?

As the name suggests, UnboundLocalErrors are only raised when improperly referencing an unassigned local variable. In most cases this will occur when trying to modify a local variable before it is actually assigned within the local scope. To illustrate we’ll get right into our sample code and the increment_local_count() function:

As you can see the increment_local_count() function does just what the name implies: trying to increment the local count variable by one and then outputting the result. However, there’s a distinct lack of assignment for the count variable in the local scope of our function block, so executing this code raises an UnboundLocalError:

That sort of makes sense. Since no count variable could be located by the parser no resolution can occur for the increment statement. However, this may look very similar to the NameError we looked at previously, which is raised when “global or local names are not found.” So, what makes UnboundLocalError different from NameError? We can illustrate this difference in our second test function, set_locaL_book_title(title):

Just as with the increment test we’re trying to use a local variable (book) that has not been assigned within the local function scope. However, executing this function raises a NameError, rather than an UnboundLocalError:

As we can see by the produced error message the difference here is that, since we’re referencing the title property of book, the compiler assumes that book is actually a global name, so the CPython interpreter evaluates the instruction before it even reaches the secondary instruction that references the title property we’re ultimately attempting to reference. We can use the dis module to disassemble our functions and see the full bytecode that the CPython interpreter actually processes during execution. We won’t go into full detail of these instructions and how the interpreter parses them, but check out our NameError article from last week for more details.

Here is the bytecode instruction set for the book.title = title source code line in set_local_book_title(title):

As we can see, the first instruction is LOAD_GLOBAL using the book argument, indicating that the interpreter thinks book is a global. This is why a NameError is produced, even though book is actually an undefined local in this case.

One way to resolve this is by using the special global statement to reference a global variable that is assigned outside the local function scope. Here we see global_book is assigned to a value outside of the set_global_book_title(title) function scope:

However, if we run this test function we’re able to successfully update the global_book.title property:

Just as within set_local_book_title(title), the bytecode of set_global_book_title(title) shows the global_book.title = title source code statement contains the LOAD_GLOBAL instruction for the global_book object, but our use of the global statement informs the interpreter to actually seek out the globally-scoped name for reference:

Airbrake’s robust error monitoring software provides real-time error monitoring and automatic exception reporting for all your development projects. Airbrake’s state of the art web dashboard ensures you receive round-the-clock status updates on your application’s health and error rates. No matter what you’re working on, Airbrake easily integrates with all the most popular languages and frameworks. Plus, Airbrake makes it easy to customize exception parameters, while giving you complete control of the active error filter system, so you only gather the errors that matter most.

Check out Airbrake’s error monitoring software today and see for yourself why so many of the world’s best engineering teams use Airbrake to revolutionize their exception handling practices!