- Introduction to Python Namespaces
- Python Scopes
- Python Re-scoping Statements
- Python Namespaces and Scopes Practice Problems
Introduction to Python Namespaces
In the previous series of tutorials, we haven’t concerned ourselves too much with the use of variable names within our terminal and scripts. However, as our programs become larger and require more functions and modules, it’s important to know how Python uses variable names to perform actions.
We’ve mentioned before how every “thing” in Python is an object. When we assign an integer x = 1
, we are telling Python that when we reference x
, we mean for Python to point to the integer-type object 1
for any usage of the value of 1
for numeric calculations or other methods. A “namespace” is a dictionary that Python uses to lookup the given variable name, and retrieve the object associated with it. This is formally called “mapping” the variable names to objects.
To obtain the listing of names that Python can access, we can use the dir
function. We’ve previously used this function to determine what attributes of an object were accessible, but by executing the function without an argument we can return a list of all the variables within the current namespace. If we start a new terminal or module and execute dir()
, we can see the following:
dir()
> ['__annotations__', '__builtins__', '__doc__',
> '__loader__', '__name__', '__package__', '__spec__']
These are the high-level objects available for access from a plain instance of Python within a terminal or module.
You may also remember in our tutorial on Python control flows, we used the id
function to determine the id of Python objects.
id(__builtins__)
> 1535598892600
Python Scopes
The reason why we want to know more about these namespaces is because we want to know what variables are available for use at a given time. In the context of Python namespaces, a “scope” is the collection of names associated with a particular environment.
Python has the following scopes:
When a name is referenced in Python, the interpreter searches for it in the namespaces starting from the smallest scope in the above diagram, and progressively moves outward until Python either finds the name or raises a NameError exception. Now let’s look into these scopes in detail.
Python Builtin Scope
The builtin scope contains all of the Python functions that are builtin to vanilla Python. These include common functions such as print
and dir
. A list of all the Python builtin functions can be found here Whenever one of the builtin functions is specified, Python will search until it finds the name of the function in the builtin scope’s namespace. The builtin scope is the widest scope Python uses, which means that if any name within the builtin scope is defined again within a smaller scope, then the name within the smaller scope will be used. This can be detrimental if we use a builtin name as a new variable. For example:
type(print) # First look at the builtin function
> builtin_function_or_method
print = 10 # Assign 10 to a variable named "print"
type(print)
> int
print("Now let's print something.")
> TypeError: 'int' object is not callable
Oh no! By defining a variable with a name that conflicts with a builtin name, we’ve effectively removed the capability to use the print
function. However, we haven’t deleted the print
function, it’s just not the first name reached in the scope. The print
function still exists, but to reach it we’ll have to manually specify builtin scope to find it. This can be done by specifying the builtin module, __builtin__
that is imported automatically when the Python interpreter is started.
__builtin__.print("Now it will work.")
> Now it will work.
Code More, Distract Less: Support Our Ad-Free Site
You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.
Python Module Scope
One step below the builtin namespace scope is the module scope. The module scope consists of all names defined within the executing module or terminal within Python, outside of any class or function. This is the namespace we generally think of when we assign objects to variables like so:
x = 2 # x is now defined within the module namespace
This means that objects defined within the module have access to this namespace if they do not exist within an enclosing scope:
x = 2
def f():
print(x)
f()
> 2
This may seen counterintuitive based on our previous discussions on functions. In the above example, x
was not defined within the function f
, therefore we would think that the interpreter would raise a NameError exception. However, because of the overlapping scopes model Python employs, the variable name x
was searched for in the higher-level scope (the module scope in this case) and returned to the function.
Caution: While we have seen in the above example that we can reference names in a higher-level scope, this method of object reference should be avoided. Because the variable could be found in any of the higher levels of scopes, there can be ambiguity within smaller scopes as to which variable is being referenced. This is why we follow the advice given in the functions tutorial: assume that only variables passed into the input of a function can be used within the function, and assume that only variables returned by the function can be used in the main program.
Python Local and Enclosing Scopes
The local scope is the namespace of the “current” level of the program. This is either within a function, class, or imported module that is not the main module. For example:
x = 2 # x is now defined within the module namespace
def function():
x = 3 # x is now defined within the local namespace of function
Bear in mind the local namespace is not the lowest level of all the nested functions, classes, or modules, but rather the level on which the code is executing. For example:
x = "2" # x is now defined within the module namespace
def example():
x = "3" # x is now defined as 3 within the local namespace of example
def method():
x = "4" # x is now defined as 4 within the local namespace of method
def function():
x = "5" # x is now defined as 5 within the local namespace of function
print("Function Scope: " + x)
function()
print("Method Scope: " + x)
method()
print("Example Scope: " + x)
example()
print("Module Scope: " + x)
> Function Scope: 5
> Method Scope: 4
> Example Scope: 3
> Module Scope: 2
Namespaces between a local scope and the module scope are considered enclosing scopes. In the above code, the method
function has x
defined as 4 within its local scope, 3 in the enclosing example
scope, and 2 in the module scope. The method
scope does not have access to the x
defined as 5 within the function
scope.
Python Re-scoping Statements
Now that we’ve seen how Python searches for variables, let’s focus on how we can manually assign variables to different namespaces. Being able to shift the scope of a defined variable outside of its local namespace can be important.
For example, in the last code block of the previous section, we couldn’t access the x
variable that was assigned to 5 in the smallest scope. The proper way to pass variables between scopes is to pass objects into a function through the input tuple and return the values through the function return
call. However, there are some instances where this can be tricky to perform. Python has established the global
and nonlocal
statements for this purpose.
Python global Statement
In any namespace, declaring a variable with the global
statement will store and retrieve that variable immediately from the module scope. For example:
x = "2" # x is now defined within the module namespace
def example():
x = "3" # x is now defined as 3 within the local namespace of example
def method():
global x # x will now be defined as being within the module scope
x = "4" # x is now defined as 4 within the local and module namespace
def function():
x = "5" # x is now defined as 5 within the local namespace of function
print("Function Scope: " + x)
function()
print("Method Scope: " + x)
method()
print("Example Scope: " + x)
example()
print("Module Scope: " + x)
> Function Scope: 5
> Method Scope: 4
> Example Scope: 3
> Module Scope: 4
Within the method
namespace we declared x
as being a global variable, or a variable within the module namespace. After the global
statement, changes to the x
variable changed the variable within the module scope as well, leading to the final print of the module scope x
as 4.
Notice that in the above code, defining x
as global within one namespace did not forever declare that the name was in the module scope. You can see that after function
assigned the value to be 5, it only persisted within the function
namespace. The global
statement only serves to declare that a variable within the namespace in which it is invoked is defined within the module scope.
Python nonlocal Statement
Where the global
statement serves to declare a variable within the module scope, the nonlocal
statement declares that the variable is within the scope of the next enclosing namespace. For example:
x = "2" # x is now defined within the module namespace
def example():
x = "3" # x is now defined as 3 within the local namespace of example
def method():
nonlocal x # x will now be defined as being within the example scope
x = "4" # x is now defined as 4 within the local and example namespace
def function():
x = "5" # x is now defined as 5 within the local namespace of function
print("Function Scope: " + x)
function()
print("Method Scope: " + x)
method()
print("Example Scope: " + x)
example()
print("Module Scope: " + x)
> Function Scope: 5
> Method Scope: 4
> Example Scope: 4
> Module Scope: 2
We can see that the nonlocal
statement allowed Python to declare the variable x
within the method
namespace as also being within the next enclosing namespace example
. This did not alter the assignment of the variable within any of the outer scopes, as the module namespace still had x
assigned to 2.
Note that the nonlocal
statement will fail if the next enclosing scope is the module scope:
x = "2" # x is now defined within the module namespace
def example():
nonlocal x # x will now be defined as being within the example scope
x = "3" # x is now defined as 3 within the local namespace of example
> SyntaxError: no binding for nonlocal 'x' found
If the next enclosing scope is the module scope, then the global
statement will need to be used instead.
Code More, Distract Less: Support Our Ad-Free Site
You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.
Python Namespaces and Scopes Practice Problems
- Without running the code, predict the output of the following:
x = "2"
def example():
x = "3"
def method():
global x
x = "4"
def function():
nonlocal x
x = "5"
print("Function Scope: " + x)
function()
print("Method Scope: " + x)
method()
print("Example Scope: " + x)
example()
print("Module Scope: " + x)
- Without running the code, predict the output of the following:
x = "2"
def example():
x = "3"
def method():
global x
x = "4"
def function():
global x
x = "5"
print("Function Scope: " + x)
function()
print("Method Scope: " + x)
method()
print("Example Scope: " + x)
example()
print("Module Scope: " + x)
- Suppose we have the following list of lists:
ll = [[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]]
We want to create two loops to read the data. The outer loop will iterate over the outer list (the “rows”), the inner loop will iterate over the inner lists (the “columns”), and both will break once a 1 is encountered. Note that once the 1 is encountered on the inner loop, it must stop the outer loop as well. Write a piece of code that uses the global
statement to accomplish this.
Solution
-
Suppose we import a module that contains some numerical packages for using the Buckingham Pi Theorem, in which there exists a function called
pi
that is not the irrational number $\pi$. we also imported themath
module that has a variablepi
that is the irrational number $\pi$. How can we tell Python which of these objects to use in a given setting? Solution -
A developer working on a project with you made a mistake in one of their modules such that an important variable you need to import directly into your program is named
open
. After importing this variable using thefrom [module] import open
, you can no longer open files using the builtinopen
function. If you must import theopen
variable direction (no[module].open
), then what is one method for accessing the builtinopen
function? Solution
Solutions
> SyntaxError: no binding for nonlocal 'x' found
Recall that the nonlocal
statement refers to the next enclosing scope that is not the module scope. While the next enclosing scope was the method
scope, the x
variable was declared a global
statement within that enclosing scope, therefore the x
variable was effectively in the module scope.
> Function Scope: 5
> Method Scope: 5
> Example Scope: 3
> Module Scope: 5
3. Python has no method of breaking an outer loop from within an inner loop, therefore we will have to either pass a flag variable to the outer loop from an inner loop function, or use a global
variable.
break_loops = False
def inner(inlist): # Define an inner reading loop function
global break_loops
for i in inlist:
if i == 1:
break_loops = True
break
for i in ll:
inner(i)
if break_loops:
break
4. This is the ultimate reason why we want to avoid using the from [module] import [object]
syntax when importing modules. This format copies the object from the namespace of the target module to the module scope or global scope. If we import objects from modules using the import [module]
method, then we can explicitly tell Python in which namespace it should look for the variable with [module].pi
.
5. While in this example we cannot specify the namespace of the open
variable we imported, we can use the workaround solution of calling __builtin__.open
to access the usual open
function.
Did you find this free tutorial helpful? Share this article with your friends, classmates, and coworkers on Facebook and Twitter! When you spread the word on social media, you’re helping us grow so we can continue to provide free tutorials like this one for years to come.