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:

Python 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.

You can learn Python in half the time
I see people struggling with Python every day and I want to help. That's why I developed this systematic approach to learning Python - FAST. This powerful training program exposes you to the Python programming language in a natural way so learning is easy.

I want to join the free wellsrPRO Python Training program

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.


You can learn Python in half the time
I see people struggling with Python every day and I want to help. That's why I developed this systematic approach to learning Python - FAST. This powerful training program exposes you to the Python programming language in a natural way so learning is easy.

I want to join the free wellsrPRO Python Training program

Python Namespaces and Scopes Practice Problems

  1. 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)

    Solution

  1. 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)

    Solution

  1. 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

  1. 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 the math module that has a variable pi that is the irrational number $\pi$. How can we tell Python which of these objects to use in a given setting?     Solution

  2. 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 the from [module] import open, you can no longer open files using the builtin open function. If you must import the open variable direction (no [module].open), then what is one method for accessing the builtin open function?     Solution


Solutions

1.

> 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.

2.

> 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.