Namespaces, Scopes, and Contexts
A namespace is a binding of (i.e., a map
from) names (identifiers) to objects. Each name is unique within a
namespace. For instance, there can be only one variable x
. In Python, as in many programming
languages, namespaces are created and destroyed throughout the execution
of a program. When a main script is run, the Python interpreter creates
(and never destroys) the built-in namespace
that includes mappings for several built-in objects such as the
functions print()
, len()
, and
abs()
and
the constants True
, False
, and
None
.
As we saw in section 2.2, the names in a namespace
for an imported module a_module
begin with the name of the module, as in a_module.do_something()
. Or, if the
module was imported with an alias, as in import a_module as am
,
the names in its namespace begin with am
.
The main script or a module has a top-level namespace called the global namespace. Names defined in the script or module and outside of any function or class definition go into this top-level namespace. The execution of a function or class creates a new namespace for it. This is true for nested function and class definitions, as well. Therefore, a hierarchy of namespaces is created with nested function and class definitions. At the bottom of this hierarchy is an innermost local namespace. Levels below the global namespace and above a local namespace are called non-local namespaces (i.e., enclosing namespaces).
The scope of a name binding (to an
object) is the portion of the code of a program in which the name is
bound (i.e., where it can be used).1 The scope of x = 3
is the part of the code in which the use of x
will return that 3
. The context for a given portion of a program is the
collection of all bound names and the ordering of namespaces searched
when a name is used. The context of a scope of names in a local
namespace has the following search priority:
- Local namespace
- Non-local namespaces
- Global namespace
- Built-in namespace
For instance, consider the namespaces, scopes, and contexts for the following script:
= 3
x print(f"Global x: {x}")
def plus_7(y):
= y + 7
x print(f"Local x: {x}")
return x
plus_7(x)print(f"Global x: {x}")
This prints the following to the console:
Global x: 3
Local x: 10
Global x: 3
To interpret these results, we see that the statement x = 3
binds the name x
in the global
namespace such that Global x: 3
is
printed. The context for this portion of code is the collection of
bindings for the names in the global and built-in namespaces and the
search priority (1) global namespace and (2) built-in namespace. The
plus_7()
function definition
creates a new local namespace in which x
is bound with the assignment x = y + 7
.
The context for the function code block is the set of bindings for the
names in the local, global, and built-in namespaces and the search
priority (1) local namespace, (2) global namespace, and (3) built-in
namespace. Therefore, the use of x
here searches the local namespace
first; finding one upon the function being called, it prints (in this
case) Local x: 10
.
Finally, we see that the global namespace x
has been unchanged by the local
assignment.
In the previous example, if we remove the local assignment x = y + 7
,
what happens?
Because there is no binding of x
in the local namespace of the
function, x
is not found here.
Therefore, the global namespace is searched; the global namespace x
is found and used within the
function. This results in the program printing
Global x: 3
Local x: 3
Global x: 3
Note that if x
had not been
found in the global namespace, the built-in namespace would have been
searched. Because this namespace also lacks a binding for x
, a NameError
would
be raised.
The use of global or non-local names within a function or class
definition is generally discouraged. It is difficult to read and debug
code that refers to names outside of its local namespace. We prefer to
pass necessary objects through input arguments. Even worse than the use
of global names within a function or class definition is their
reassignment or their bound object’s mutation. Rarely necessary and
nearly always a bad idea, this can be achieved with the use of the global
and
nonlocal
keywords. Without these keywords, global and non-local names are
read-only. With their use, global and non-local names can be reassigned
and bound objects mutated, as in the following example:
= 3
x print(f"Global x: {x}")
def plus_7(y):
global x
= y + 7
x print(f"Local x: {x}")
return x
plus_7(x)print(f"Global x: {x}")
This prints the following to the console:
Global x: 3
Local x: 10
Global x: 10
So we have altered x
in the
global namespace. Again, it is inadvisable to use this unless absolutely
necessary.
Sometimes the term “scope” is used to mean what we call a “context of a scope.” We will try to avoid this usage, but it is quite common.↩︎
Online Resources for Section 2.4
No online resources.