= 'object' variable_name
Namespaces, scopes, & side effects
Unit 07
Namespaces and scopes
This Section is adapted from Sebastian Raschka’s beginner’s guide to python namespaces and scopes.
In Python, a namespace is a container where names (e.g., variable names, function names, etc.) are mapped to objects. In the example
variable_name
is mapped to the string object
. This mapping is stored in a namespace (implemented as a dictionary, a_namespace = {'variable_name':'object', ...}
). The same name can only be used once per namespace, but can exist in different namespaces without conflict. For example, there is the global namespace of your python script or notebook that contains the names of all loaded modules, assigned variables, and defined functions. Each time a new function is called a local namespace includes all local names inside the function. The local namespace only lasts until the function returns.
Scope refers to the region of the code where a particular variable is accessible. A variable’s scope is determined by where it is defined. The concept of scope is closely related to namespaces in Python. While the namespace defines the mapping from names to objects, the scope determines the visibility and accessibility of these names in various parts of your code.
Python will first try to find a variable name in the local scope. This can, for example, be within the current function definition or the current for
-loop. If it doesn’t find the name in the local scope, it searches for the name in the enclosed scope, which could be a function enclosing another function or a loop. If there is no enclosing scope, or if the variable name is not defined in the enclosing scope either, it searches in the global scope. The global scope refers to the uppermost level of the current script or notebook or module. The last scope where python checks is the built-in scope, where for example the function len
is defined.
For example,
# Global scope
= 'global variable'
a
def a_func():
# Local scope
= 'local variable'
b print(a, '[ a inside a_func() ]') # can access global variable
print(b, '[ b inside a_func() ]') # can access local variable
a_func()print(a, '[ a outside a_func() ]') # can access global variable
## the following would cause a NameError, because `b` is not known in global scope
print(b, '[ b outside a_func() ]') # can not access local variable from different scope
Brain twisters
Can you guess what the following code snippets will produce?
- Let’s define a local variable with the same name as a global variable
= 'global value'
a
def a_func():
= 'local value'
a print(a, '[ a inside a_func() ]')
a_func()print(a, '[ a outside a_func() ]')
Before you read the explanation, run the code block yourself and verify its output.
When we call a_func()
, it will first look in its local scope for a
. Since a
is defined in the local scope of a_func
, its assigned value local value
is printed. Note that this doesn’t affect the global variable, which is in a different scope.
- Following the same logic, let’s briefly add an enclosing scope. What does the code snippet produce?
= 'global value'
a
def outer():
= 'enclosed value'
a
def inner():
= 'local value'
a print(a)
inner()
outer()
Before you read the explanation, run the code block yourself and verify its output.
Let us quickly recapitulate what we just did: We called outer()
, which defined the variable a
locally (next to an existing a
in the global scope). Next, the outer()
function called inner()
, which in turn defined a variable with the name a
as well. The print()
function inside inner()
searched in the local scope first, and therefore it printed the value that was assigned in the local scope.
Side effects
We will stay with the same topic, but move from scalar variables to containers, here: lists and tuples. Can you still remember their difference?
= ['global', 'list']
a_list = ('global', 'tuple')
a_tuple
def a_func(a_list, a_tuple):
print(a_list, 'input to a_func')
print(a_tuple, 'input to a_func')
# a_list = a_list.copy()
0] = 'local'
a_list[= ('local', 'tuple')
a_tuple print(a_list, 'modified in function scope')
print(a_tuple, 'modified in function scope')
return a_list, a_tuple
a_func(a_list, a_tuple)print(a_list, 'the end')
print(a_tuple, 'the end')
Please take a few moments to fully understand what exactly is going on here: We define a_list
and a_tuple
in the global scope and use the two variables as inputs to a function. Within the function, the first element of a_list
is modified. Since lists are mutable, this change affects a_list
outside the function as well. Since tuples are immutable, the function cannot change a single element of a_tuple
, but instead creates an entire new tuple with the same name. This assignment happens within the function, so it only affects the local scope, but not the original a_tuple
outside the function. After the function call, a_list
outside the function reflects the changes made inside the function, while a_tuple
remains unchanged.
A function that modifies its input parameters is said to have side effects. Avoid writing functions with side effects. Try to make functions pure, meaning their outputs should only depend on their inputs, and they shouldn’t modify their inputs. Instead of modifying an input parameter, consider creating a new object within the function and returning it. This makes your functions more predictable and less prone to bugs.
- Use the
copy()
method to create a new object which is a copy of the original object. For example, uncomment the linea_list = a_list.copy()
in the code example above and verify the different outcome. copy()
only creates a shallow copy of your object. When you are dealing with complex data structures like lists of lists or dictionaries containing lists, you will have to create what’s called a deep copy.deepcopy()
from thecopy
module recursively copies all nested objects.
from copy import deepcopy
= deepcopy(a_nested_list) a_nested_list
Learning checklist
- I have a fundamental understanding of the concept of namespace and scope in python.
- I will avoid writing functions with side effects, by creating copies (or deep copies) of input parameters that the function mutates.