Blog Home Kelvin Jackson

Python Scope is Confusing

A few weeks ago I was reminded that identifier scope in Python can be very counterintuitive, especially after spending a lot of time programming in JavaScript.

I was writing something that could be summarized as this:

def f(): some_variable = "Value" def inner_function(): some_variable = "New Value" inner_function() return some_variable print(f())

The output I was hoping for, as you may have guessed, was New Value. Unfortunately, as I hadn't written anything particularly complicated in Python for a while at that point, I was stumped to find that the program consistently printed out Value instead.

After a bunch of testing and a bit of head-scratching, I finally saw the problem: there needs to be a nonlocal statement inside inner_function():

def f(): some_variable = "Value" def inner_function(): nonlocal some_variable some_variable = "New Value" inner_function() return some_variable print(f())

Now the program does what I expected it to.

In theory, I knew that I needed the nonlocal statement. I implemented a large subset of Python in one of my undergrad Computer Science classes, and getting local, nonlocal and global variables to behave as they were supposed to was definitely part of that (and it was a huge pain). But nevertheless, after building a few largish projects in JavaScript and not using Python as much (or at least, not using Python for projects that required modifying closed-over variables from inside a nested function), they were no longer intuitive, if ever they had been in the first place.

Which brings us to the main points of this post. The first, of course, is to remind Python programmers to be careful of quirks like this in Python scope, especially since they tend to surface only in contexts that occur with fairly low frequency, reducing the chances of the programmer developing good reflexes for them when they are just starting to learn the language.

The other point, however, is that programming language designers should really think long and hard about how to make scope, shadowing, and variable declaration as intuitive as possible for the user. Unmarked shadowing can be confusing enough; including unmarked shadowing in addition to unmarked variable declaration is just asking for painful bugs.