Scope¶
Indices and tables¶
In Python, like many other languages, it is possible to define variables (names) at many different points in your code. Knowing the rules the interpreter will use for finding the correct variable to manipulate is important for producing bug free code. The rules aren’t all that difficult and are summarized in this section.
Basic Rules¶
Python has the concept of a code block. There are several things that are considered code blocks, but, the most important ones are:
- A module (a file or package)
- A function body
- A class definition
Variables (names) must be defined before they are referenced.
Variables (names) have a scope over which they are visible. The scope of a variable depends on where it was created (i.e. in which block):
- Variables (names) created at the module level are global and can be seen by all code enclosed in that module.
- Variables (names) created inside a block are by default local to (i.e. only visible to) the code in that block and any blocks nested inside of it.
Todo
Need to add a section in classes about the fact that names defined at the top of the class are not availabe to the class’ methods by default (see last paragraph of here. Must mark method with
@classmethod
, putcls
as the first parameter, and usecls.var_name
to access.As a corollary to the first 2 items, module level variables are therefore both global and local.
Variables (names) not defined in the block in which they are used and which are also not global, are called free variables. In the code samples below,
x
is a free variable when used inbar()
:>>> def foo(): >>> x = 3 >>> def bar(): >>> print(x) >>> bar() >>> foo() 3
Variables (names) are found (resolved) by finding the nearest enclosing scope that contains a variable (name) that matches (see below for details on how to modify this behavior).
If a variable is not found, a NameError
or UnboundLocalError
is thrown by the interpreter.
Pretty straight forward and sensible. But, lets look at some examples to help solidify things:
In the code below, the variable x
is defined at module level, therefore it is global and thus visible to all code, as shown with the highlighted lines.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Create global variables at module level
x = 1
# Define a function (creates new block and thus new scope)
def bar():
y = 2
# Define nested function (creates new block and thus new scope)
def baz():
z = 3
print("baz level 'z': ", z)
# Call nested baz() from within bar()
baz()
print("bar level 'y': ", y)
# Call bar()
bar()
print("mod level 'x': ", x)
|
In the code below, the variable y
is defined within a function body, therefore it is local to that function body and is thus visible from it’s point of declaration on-ward, including into any nested function bodies, as shown with the highlighted lines.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Create global variables at module level
x = 1
# Define a function (creates new block and thus new scope)
def bar():
y = 2
# Define nested function (creates new block and thus new scope)
def baz():
z = 3
print("baz level 'z': ", z)
# Call nested baz() from within bar()
baz()
print("bar level 'y': ", y)
# Call bar()
bar()
print("mod level 'x': ", x)
|
In the code below, the variable z
is also defined in a function body, therefore it is local to that function body and is thus visible from it’s point of declaration on-ward just like y
was. However, in this example, there are no further nested functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Create global variables at module level
x = 1
# Define a function (creates new block and thus new scope)
def bar():
y = 2
# Define nested function (creates new block and thus new scope)
def baz():
z = 3
print("baz level 'z': ", z)
# Call nested baz() from within bar()
baz()
print("bar level 'y': ", y)
# Call bar()
bar()
print("mod level 'x': ", x)
|
We can also flip this around and look at it from the opposite direction by determining the list of scopes that are visible to a block of code. This list of scopes a block can see is called the blocks environment. Consider the following examples:
In the following example, the bar()
function sees bar()
’s scope and global scope, as shown with the highlighted lines.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Create global variables at module level
x = 1
# Define a function (creates new block and thus new scope)
def bar():
y = 2
# Define nested function (creates new block and thus new scope)
def baz():
z = 3
print("baz level 'z': ", z)
# Call nested baz() from within bar()
baz()
print("bar level 'y': ", y)
# Call bar()
bar()
print("mod level 'x': ", x)
|
In the following example, the baz()
functions sees baz()
’s scope, bar()
’s scope and global scope, as shown with the highlighted lines.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Create global variables at module level
x = 1
# Define a function (creates new block and thus new scope)
def bar():
y = 2
# Define nested function (creates new block and thus new scope)
def baz():
z = 3
print("baz level 'z': ", z)
# Call nested baz() from within bar()
baz()
print("bar level 'y': ", y)
# Call bar()
bar()
print("mod level 'x': ", x)
|
Warning
The search for variables is done during execution, not bytecode compile time.
In the code below, even though val
was defined prior to foo()
, val
is updated with a new value before foo
is called. This will cause foo()
to print the value 200, not 10.
>>> val = 10
>>> def foo():
>>> print(val)
>>> val = 200
>>> foo()
200
(Motivation for this example taken from the Python language reference [1])
Tweaking The Rules¶
Python provides some built-in functions for altering how it finds variables (names).
First, consider the following example as the base case. Each function has access to its own local variable x
. When each function is executed, it only modifies its local variable x
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # Create a global variable at module level
x = 1
print("mod level 'x': ", x)
# Define a function (creates new block and thus new scope)
def bar():
x = 2
print("bar level 'x': ", x)
# Define nested function (creates new block and thus new scope)
def baz():
x = 3
print("baz level 'x': ", x)
# Call nested baz() from within bar()
baz()
print("bar level 'x': ", x)
# Call bar()
bar()
print("mod level 'x': ", x)
|
The above code produces the following output:
mod level 'x': 1
bar level 'x': 2
baz level 'x': 3
bar level 'x': 2
mod level 'x': 1
We can use the nonlocal()
function to tell Python that we want to refer to a variable in an enclosing function block. In the code below. the baz()
function modifies x
in an enclosing scope, in this case that of bar()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # Create a global variable at module level
x = 1
print("mod level 'x': ", x)
# Define a function (creates new block and thus new scope)
def bar():
x = 2
print("bar level 'x': ", x)
# Define nested function (creates new block and thus new scope)
def baz():
nonlocal x
x = 3
print("baz level 'x': ", x)
# Call nested baz() from within bar()
baz()
print("bar level 'x': ", x)
# Call bar()
bar()
print("mod level 'x': ", x)
|
The above code produces the following output:
mod level 'x': 1
bar level 'x': 2
baz level 'x': 3
bar level 'x': 3
mod level 'x': 1
Finally, we can use the global()
function to tell Python that we want to refer to a variable in the global scope. In the code below, the baz()
function modifies x
in the global scope:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # Create a global variable at module level
x = 1
print("mod level 'x': ", x)
# Define a function (creates new block and thus new scope)
def bar():
x = 2
print("bar level 'x': ", x)
# Define nested function (creates new block and thus new scope)
def baz():
global x
x = 3
print("baz level 'x': ", x)
# Call nested baz() from within bar()
baz()
print("bar level 'x': ", x)
# Call bar()
bar()
print("mod level 'x': ", x)
|
The above code produces the following output:
mod level 'x': 1
bar level 'x': 2
baz level 'x': 3
bar level 'x': 2
mod level 'x': 3
Tip
As a general rule, don’t write code that changes variables that were defined outside the current scope. Doing so just makes it confusing to read, understand, test, debug and maintain that code.
Search the Global Namespace¶
Earlier, it was noted that Python searches the various scopes to find the nearest enclosing scope that contains a variable (name) that matches. As discussed, the outer most scope Python can search is the global scope. This is mostly correct. What wasn’t mentioned until now is that if the name still isn’t found by the time the search of the global scope completes, Python will search one more area, the builtins module. The builtins module contains all the built-in functions and built-in constants that are part of the Python interpreter and are always available.
In addition to viewing documentation about the builtins online, you can view it from within the interpreter. The builtins module is a special module, so, it uses Python’s naming scheme for special names (see Identifiers) which uses dunder-dunder notation. Specifically, __builtins__
.
Give it a try:
>>> dir(__builtins__)
['ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
...
'tuple',
'type',
'vars',
'zip']
Also try:
>>> help(__builtins__)
[1] | https://docs.python.org/3.5/reference/executionmodel.html#interaction-with-dynamic-features |