Learn Computing from the Experts | The Rheinwerk Computing Blog

What Are Namespaces in Python Programming?

Written by Rheinwerk Computing | Aug 26, 2024 1:00:00 PM

In a simple look at function bodies in Python, they can be considered as an encapsulated section that can exchange information with the main program only via parameters or the return value.

 

At first, this isn’t a bad way of looking at things for Python programmers because it keeps your interface "clean." In some situations, however, it makes sense to let a function act beyond its local namespace, which will be described in this blog post.

 

Accessing Global Variables: global

First, we must distinguish between two concepts. If we are in the context of a function—that is, in the function body—then we can of course create and use references and instances there. However, these are only accessible inside the function body during a function call. They exist in the local namespace. In contrast, references of the main program exist in the global namespace. Likewise, a distinction is also made between global references and local references. Let's look at the following example:

 

def f():

   a = "local string"

b = "global string"

 

The distinction between the global and local namespace becomes clear with the following example:

 

>>> def f(a):

...     print(a)

>>> a = 10

>>> f(100)

100

 

In this example, a reference named a exists in both the global and local namespaces. In the global namespace, it references the integer 10; in the local namespace of the function, it references the passed parameter—in this case, the integer 100. It’s important to understand that these two references – although having the same name – have nothing to do with each other as they exist in different namespaces. This figure summarizes the concept of namespaces.

 

 

Accessing the Global Namespace

In the local namespace of a function body, read access to a global reference is possible at any time as long as no local reference of the same name exists:

 

>>> def f():

...     print(s)

...

>>> s = "global string"

>>> f()

global string

 

As soon as an attempt is made to assign a new instance to a global reference, a corresponding local reference is created instead:

 

>>> def f():

...     s = "local string"

...     t = "other local string"

...     print(s, "/", t)

...

>>> s = "global string"

>>> f()

local string / other local string

>>> s

'global string'

>>> t

Traceback (most recent call last):

   File "<stdin>", line 1, in <module>

NameError: name 't' is not defined

 

A function can still assign instances to global references using the global statement. To do that, the global keyword must be written in the function body, followed by one or more names that should be treated as global references:

 

>>> def f():

...     global s, t

...     s = "local string"

...     t = "other local string"

...     print(s, "/", t)

...

>>> s = "global string"

>>> f()

local string / other local string

>>> s

'local string'

>>> t

'other local string'

 

In the function body of f, s and t are explicitly marked as a global reference and can henceforth be used as such.

 

Local Functions

You can also define local functions. These are functions created in the local namespace of another function and are only valid there like any other reference in the local namespace. The following example shows such a function:

 

>>> def global_function(n):

...     def local_function(n):

...         return n**2

...         return local_function(n)

...

>>> global_function(2)

4

 

Inside the global function called global_function, a local function named local_ function has been defined. Note that the respective parameter n doesn’t necessarily reference the same value, although it has the same name. The local function can be called in the namespace of the global function completely like any other function.

 

Because it has its own namespace, the local function doesn’t have access to local references of the global function. To still pass some selected references to the local function, we use a trick with preassigned function parameters:

 

>>> def global_function(n):

...     def local_function(n=n):

...         return n**2

...         return local_function()

...

>>> global_function(2)

4

 

As you can see, the local function no longer needs to be explicitly passed the parameter n when it’s called. Instead, it’s passed implicitly in the form of a preassigned parameter

.

Accessing Parent Namespaces: nonlocal

In the previous section, we talked about the two existing namespaces, the global and local namespaces. This subdivision is correct, but it undercuts an interesting case: local functions may also be defined within functions. Local functions, of course, again bring their own local namespace inside the local namespace of the parent function. That means that the nested function can access variables in its own namespace, variables defined in the parent function and global variables. Thus, with nested function definitions, you can’t divide the world of namespaces into a local and a global level. Nevertheless, the question also arises here of how a local function can access references that are located in the local namespace of the parent function.

 

The global keyword can’t help us here because it only provides access to the outermost, global namespace. But for this purpose, there is the nonlocal keyword. Let’s take a look at the following example:

 

>>> def function1():

...     def function2():

...         nonlocal res

...         res += 1

...         res = 1

...         function2()

...         print(res)

...

>>> function1()

2

 

Inside the function1 function, a local function2 function has been defined to increment the res reference from the local namespace of function1. To do that, res must be marked as nonlocal within function2. The notation is based on accessing references from the global namespace via global.

 

After function2 is defined, res is defined in the local namespace of function1 and associated with the value 1. Finally, the function2 local function is called and the value of res is output. In the example, function1 outputs the value 2.

 

The nonlocal keyword can also be used with multiple, nested functions, as the following extension to the previous example shows:

 

>>> def function1():

...     def function2():

...         def function3():

...             nonlocal res

...             res += 1

...         nonlocal res

...         function3()

...         res += 1

...     res = 1

...     function2()

...     print(res)

...

>>> function1()

3

 

Now an additional local function function3 has been defined in the local namespace of function2. The res variable can also be incremented from the local namespace of function3 using nonlocal. The function1 function outputs the value 3 in this example.

 

In general, the way nonlocal works for deeper function nesting is that it moves up the hierarchy of namespaces and binds the first reference with the specified name into the namespace of the nonlocal keyword.

 

Unbound Local Variables: A Stumbling Block

In this section, we’ll describe a stumbling block when using local and global variables with the same name. In the following example, we first define a global variable called name and then a function called hello that outputs a friendly greeting to the person defined by name:

 

>>> name = "Peter"

>>> def hello():

...     print("Hello,", name)

>>> hello()

Hello, Peter

 

As we have discussed before, the global variable can be accessed via the name identifier within the function. Creating a local variable of the same name in the namespace of the hello function is also possible and works as expected:

 

>>> name = "Peter"

>>> def hello():

...     name = "Johannes"

...     print("Hello,", name)

>>> hello()

Hello, Johannes

 

Now we’ll try to combine both variants by first outputting the value of the global variable, but then creating and outputting a local variable with the same name:

 

>>> name = "Peter"

>>> def hello():

...     print("Hello,", name)

...     name = "Johannes"

...     print("Hello,", name)

...

>>> hello()

Traceback (most recent call last):

   ...

UnboundLocalError: cannot access local variable 'name' where it is not associate d with a value

 

You can see that the first access to the name variable in this case fails with an Unbound- LocalError. The reason is that the name identifier in the local namespace of the hello function is already reserved for a local variable at the time of compiling the function. This reservation won’t change during the runtime of the function. So even though we haven't created the local variable name yet, its name is already reserved for a local variable at the time of the first print call.

 

A similar effect can be achieved if we remove the local variable name from the local namespace in the course of the function execution using del:

 

>>> name = "Peter"

>>> def hello():

...     name = "Johannes"

...     print("Hello,", name)

...     del name

...     print("Hello,", name)

>>> hello()

Hello, Johannes

Traceback (most recent call last):

   ...

UnboundLocalError: cannot access local variable 'name' where it is not associated with a value

 

Note in this case that the UnboundLocalError only occurs on the second access to name— that is, after the output of "Hello, Johannes".

 

Editor’s note: This post has been adapted from a section of the book Python 3: The Comprehensive Guide by Johannes Ernesti and Peter Kaiser.