Understanding Namespaces in Python

A namespace is a filing system used by Python to track all the names given to objects when you write a program.

When I first began trying to understand namespaces in Python, the tutorials I came across were confusing and less than helpful in their descriptions. Today I will take you on a logical and linear journey through namespaces to aid your understanding.

You can watch the accompanying video tutorial as you go over the code examples and explanations provided in this tutorial:

What Is A Namespace?

When you create code, you assign names. They may be variable names, function names, classes, or modules you import. Whichever form they take, Python must accurately store all names and any entities attached to them to allow easy and correct selection.

It does this by use of the dictionary data type, with which we are all familiar. If I assign or bind an entity to a name, Python will place that into a namespace dictionary using the name as a key and the entity as a value.  Here I assign the string 'Friday' to a variable named 'day'. Python stores this as a key: value pair in a namespace.

day = "Friday"

# Python stores this as:
{'day': 'Friday'}

It’s important to understand that there is not just one namespace but there can be many. There are also four types of which to be aware. We’ll discuss that now.

The Four Types of Namespace

Python utilizes four types of namespace. These are:

  1. Built-in Namespaces
  2. Global Namespaces
  3. Enclosing Namespaces
  4. Local Namespaces

Think of this structure as a hierarchy.

Built-In Namespace

At the top of the hierarchy, we have all the built-in names that come with Python once you open your interpreter but before you begin coding. These consist of exceptions, object types, and built-in functions. You can access these from Python either by typing dir(__builtins__) from the command line or print(dir(__builtins__)) from within your code. Here’s the result:

print(dir(__builtins__))

# Result

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

Looking through the list, you’ll see a few built-in functions such as any(), or all(), and object types such as 'float' or 'str'.

Global Namespace

Next, we have the global namespace. This space contains the names you create within the main body of the program you write.

Python creates the global namespace when you run your program and forgets it when the program terminates. You can use a command to look at the global namespace contents, unsurprisingly called globals() that will return a dictionary of all the names created at the main level of the program. Here’s an example where I’ve imported a module called 'datetime', and I’ve assigned the phrase "This is a global variable"  to a variable with the name of string.

import datetime

string = "This is a global variable"

print(globals())

# Result

{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000014732854760>, 
'__spec__': None, '__annotations__': {}, 
'__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\David\\Desktop\\Upwork Platform\\Namespace\\namespace.py',  '__cached__': None, 
'datetime': <module 'datetime' from 'C:\\Users\\David\\AppData\\Local\\Programs\\Python\\Python39\\lib\\datetime.py'>,
'string': 'This is a global variable'}

Note that the datetime key and its corresponding value is the second to last pair in this dictionary, with the last pair being the string key with its phrase value.

Enclosing and Local Namespace

Whenever you create a function in your program, Python creates a new namespace to differentiate it from other functions. In the following code, we have two functions, one nested inside the other; therefore, Python creates two namespaces. One for printString(), another for printString1().

With this nested structure, the outer function, printString(), is called an enclosing function, the inner function, printString1(), is called an enclosed function.

You can use a command to look at the local namespace within each function, called locals(). Let’s first use it on the enclosing function printString().

import datetime

string = "This is a global variable"

def printString():
       string = "This string is held in an enclosing namespace"

    def printString1():
        string = "This string is held in a local namespace"

    printString1()
    print(locals())

printString()

# Result

{'string': 'This string is held in an enclosing namespace', 'printString1': <function printString.<locals>.printString1 at 0x000001C7F0C76940>}

So this returns the key:value pair for the string variable, and the key:value pair for the printString1() function. Now let’s call locals within the enclosed function.

import datetime

string = "This is a global variable"

def printString():
    string = "This string is held in an enclosing namespace"

    def printString1():
        string = "This string is held in a local namespace"
        print(locals())

    printString1()

printString()

# Result

{'string': 'This string is held in a local namespace'}

This time the locals() call, when done within the printString1 function, returns the key:value pair for the string variable.

Points to note are:

  • The printString() function name is in the global namespace.
  • The names within the printString() function (the string key:value pair, and the printString1() function name) are in an enclosing namespace.
  • The name within the printString1() function (the string key:value pair) is in an enclosed namespace.
  • Calling locals() in any namespace will return just those names relevant to that namespace

LEGB and Scope

In the code we are using, you’ll note multiple occurrences of the variable 'string'. So how does Python know which variable is the one we want when we call it? Correct selection depends on a names 'scope'.

As you’ve seen in the code examples so far, a name is contained and has meaning within a specific scope. When you run your code, Python looks at where the name was defined and where you referenced the name in the code.

  • In our example, if we reference the variable 'string', from within the printString1() function, Python first looks at the printString1() namespace, so it looks locally.
  • If it doesn’t find the variable locally, it expands its search to the enclosing function namespace and searches there.
  • If the variable can’t be located, it then looks globally; finally, if nothing is found there, it searches the built-in namespace.
  • If it doesn’t find the variable in the built-in namespace, you’ll receive a NameError.

Let’s prove that with some code examples. I’ll start by referencing the string locally by asking Python to print the string, then each time, remove another level of variable, forcing Python to go searching until we receive the NameError exception.

string = "This is a global variable"

def printString():
    string = "This string is held in an enclosing namespace"

    def printString1():
        string = "This string is held in a local namespace"
        print(string)

    printString1()

printString()

# Result

This string is held in a local namespace

Now let’s remove the variable in printString1() but still call it from there.

string = "This is a global variable"

def printString():
    string = "This string is held in an enclosing namespace"

    def printString1():
        print(string)

    printString1()

printString()

# Result

This string is held in an enclosing namespace

Now we’ll take out the variable in printString().

string = "This is a global variable"

def printString():
    
    def printString1():
        print(string)

    printString1()

printString()

# Result

This is a global variable

Finally, we’ll remove all references to the variable 'string'.

def printString():

    def printString1():
        print(string)

    printString1()

printString()

# Result

NameError: name 'string' is not defined

So we called the variable at the deepest level of the hierarchy, yet Python still managed to find a variable of that name while one existed in the code by following the search route L-E-G-B. It searches the local namespace, then the enclosing namespace, the global namespace, and the built-in namespace.

If I called the variable from the printString() function, Python would search that function first as the namespace local to the call before looking at the global namespace and the built-ins.

In Summary

We introduced namespaces in Python and understood that namespaces are simply a filing system Python uses to store the names of all objects referenced in the code. Python stores these names in a dictionary data type.

We learned four different types of namespace; local, enclosing, global and built-in. The term scope was introduced, referring to boundaries within which a name has meaning and the area of code from which we referenced the name.

Finally, the acronym L-E-G-B describes the route Python takes when searching for a name when it is called. We proved that route through codes examples.

I hope that introduction to namespaces was helpful. Thanks for reading.