I recently realized I had a slightly hazy understanding of name scope in
Python. I knew I needed to use global
or nonlocal
in some instances, but I wasn’t
exactly clear when and where they were needed. In practice this means
scattering them everywhere they might be used.
I think I figured it out, and it’s really quite simple, but I didn’t find as clear a statement as I would have liked in the documentation. This post draws on the official Python 3 documentation and PEP 3104 - Access to Names in Outer Scopes, as well as my own testing.
The basic rules are simple:
- Python code can refer to any name in any enclosing scope, without a
global
ornonlocal
declaration. Python will search upwards, starting with the current scope, looking for the closest assigned name it can find. - Python code can assign to a new name only within its local scope.
- Python code can rebind a name only within its local scope, unless the name
within the local scope has previously been given the
nonlocal
keyword orglobal
keyword.
What does nonlocal
do? It “prevents x
from becoming a local name in the
current scope. All occurrences of x
in the current scope will refer to the
x
bound in an outer enclosing scope” (PEP 3104). That’s “an” outer enclosing
scope, i.e., it looks upward to the closest scope with the name bound, or
raises an exception if none can be found. It raises an exception in such a case
because of Rule 2: Even with one of these keywords, you can’t assign a new name
in a higher scope, you can only rebind an existing one.
What is an assignment or rebinding in this context? It is anything with an =
.
Remember that in Python this operator is overloaded to handle both assignment
and rebinding. (Although there are other operators that will assign or rebind,
like as
in with
or except
blocks, those are all locally scoped, as far as
I know.)
So here’s the TLDR practical rule:
- If your variable is on the left side of
=
, and you are intending to rebind something outside the local scope, you need eithernonlocal
orglobal
as appropriate. Otherwise, you should not use them.
Some toy examples. First, a case where nonlocal
is needed:
def test1():
x = 1
def innertest1():
print(f"Inner, can refer to any name in an enclosing scope: Expected 1, got: {x}")
def innertest2():
nonlocal x
x = 2
innertest1()
innertest2()
print(f"Outer, after rebinding with `=`: Expected: 2, got: {x}")
Second, a case where nonlocal
is not needed:
def test2():
x = []
def innertest1():
x.append(1)
innertest1()
print(f"Outer, `append` does not rebind: Expected: [1], got: {x}")
This second pattern is of course common with backtracking algorithms. When
appending to a list of combinations, nonlocal
is not needed.