top of page

Namespacing in Python

Namespacing

As with many programming languages, Python isolates code through the concept of namespaces. As a program runs, it keeps track of all the known namespaces and the information available in those namespaces.

Namespaces are helpful in a few ways:

  • As software grows, multiple concepts need similar or identical names.

  • Namespaces help minimize collisions, making it clear to which concept each name refers.

  • As software grows, it becomes exponentially more difficult to know which code is already in the codebase. Namespaces help you make educated guesses about where code might live, if it exists.

  • When adding new code to a large codebase, the existing namespaces can guide where the new code should live. If no obvious choice exists, a new namespace might be appropriate.


Namespaces are important enough that they’re included as the last statement in the Zen of Python (if you’re unfamiliar with the Zen of Python, try firing up the Python interpreter and typing import this).

Namespaces are one honking great idea — let’s do more of those!” — The Zen of Python

All the variables, functions, and classes you’ve ever used in Python were names in one namespace or another. Names are things like x or total or EssentialBusinessDomainObject which are references to something.

When your Python code says x = 3 it means, “assign the value 3 to the name x” and you can then refer to x in your code.

The word “variable” is used interchangeably with names that refer to values, though names can refer to functions, classes, and more in Python.


Namespaces and the Import Statement

When you first open the Python interpreter, the built-in namespace is populated with all the stuff built into Python. The Python built-in namespace contains the built-in functions like print() and open(). These built-ins have no prefix and you don’t need to do anything special to use them. Python makes them available to you anywhere in your code. This is why the famously easy print('Hello world!') Just Works™ in Python.

Unlike some languages, you won’t explicitly create namespaces in your Python code, but your code structure affects what namespaces are created and how they interact.

For instance, creating a Python module automatically creates an additional namespace for that module. At its simplest, a Python module is a .py file that contains some code.

A file named sales_tax.py, for example, is “the sales_tax module”:

# sales_tax.py
 
 def add_sales_tax(total, tax_rate):
 return total * tax_rate

Each module has a global namespace that code in the module can access freely. Functions, classes, and variables that aren’t nested inside anything are in the module’s global namespace:

# sales_tax.py

TAX_RATES_BY_STATE= {          (1)
    'MI': 1.06,
    # ... 
}
    
def add_sales_tax(total, state):
    return total*TAX_RATES_BY_STATE[state]  (2)

  1. TAX_RATES_BY_STATE is in the module’s global namespace.

  2. Code in the module can use TAX_RATES_BY_STATE without any fuss.

Functions and classes in a module also have a local namespace only they can access:

# sales_tax.py
TAX_RATES_BY_STATE= {
    'MI': 1.06,
         ... 
}

def add_sales_tax(total, state):
    tax_rate=TAX_RATES_BY_STATE[state]  (1)
    return total*tax_rate               (2)

  1. tax_rate is only in the local scope for add_sales_tax().

  2. Code in add_sales_tax() can use tax_rate without any fuss.

A module that wants to use a variable, function, or class from another module must import it into its global namespace. Importing is a way of pulling a name from somewhere else into the desired namespace.

# receipt.py
from sales_tax import add_sales_tax  (1)

def print_receipt():
    total= ...
    state= ...
    print(f'TOTAL: {total}')
    print(f'AFTER TAX: {add_sales_tax(total, state)}')  (2)

  1. The add_sales_tax function’s added to the receipt global namespace.

  2. add_sales_tax still knows about TAX_RATES_BY_STATE and tax_rate from its own namespace.

To refer to a variable, function, or class in Python, one of the following must be true:

  1. The name is in the Python built-in namespace.

  2. The name is the current module’s global namespace.

  3. The name is in the current line of code’s local namespace.


The precedence for conflicting names works in the opposite order; a local name overrides a global name that overrides a built-in name.

You can remember this because, generally, the definition most specific to the current code is the one that gets used. This is shown in figure 1.

Figure 1. The specificity of namespaces


You might have seen a NameError: name 'my_var' is not defined some time in your adventures with Python. That means the name my_var wasn’t found in any of the namespaces known to that code.

This usually means you never assigned my_var a value, or you assigned it somewhere else and need to import it.

Modules are a great way to begin splitting up code; if you have one long script.py with a bunch of unrelated functions, consider breaking those functions out into modules.


The Many Masks of Importing

The syntax for importing in Python seems straightforward at first, but there are a few ways to go about it and each way results in subtle differences in the information brought into the namespace.

Earlier, you imported the add_sales_tax() function from the sales_tax module into the receipt module:

# receipt.py
 
 from sales_tax import add_sales_tax

This adds the add_sales_tax() function to the global namespace of the receipt module.

This is all well and good, but suppose you add ten more functions to sales_tax and want to use them all in receipt. If you continue down the same path, you’ll end up with something like this:

# receipt.py
 
 from sales_tax import add_sales_tax, add_state_tax, add_city_tax, add_local_millage_tax, ...

This is an alternate syntax that improves on this a bit:

# receipt.py

from sales_tax import (
    add_sales_tax,
    add_state_tax,
    add_city_tax,
    add_local_millage_tax,     
    ... 
)


It’s still not great. When you need a host of functionality from another module, you can import that module in full instead:

# receipt.py
 
 import sales_tax

This adds the whole sales_tax module to the current namespace, and its functions can be referenced with a sales_tax. prefix:

# receipt.py

import sales_tax

def print_receipt():
    total= ...
    locale= ...     
    ...
    print(f'AFTER MILLAGE: {sales_tax.add_local_millage_tax(total, 
    locale)}')

This has the benefit of avoiding long import statements and the prefix helps avoid namespace collisions.


Wildcard Imports

Python allows you to import all the names from a module in shorthand using from themodule import *. It’s tempting to use this instead of prefixing those names with themodule. throughout your code, but please don’t!

Wildcard imports can cause more name collisions because you can’t see the explicit names being imported, and it makes problems hard to debug. Stick to explicit imports!



Source: Medium-Manning Publications


The Tech Platform

0 comments

Comments


bottom of page