Welcome, guest | Sign In | My Account | Store | Cart

The Zen of Python tells us:

Namespaces are one honking great idea -- let's do more of those!

Python already has an excellent namespace type, the module, but the problem with modules is that they have to live in a separate file, and sometimes you want the convenience of a single file while still encapsulating your code into namespaces. That's where classes are the usual solution, but classes need to be instantiated and methods need to be defined with a self parameter.

C++ has "namespaces" for encapsulating related objects and dividing the global scope into sub-scopes. Can we do the same in Python?

With a bit of metaclass trickery and the new ChainMap type from Python 3.3, we can!

Python, 56 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from types import ModuleType, FunctionType
from collections import ChainMap

class _NSChainedDict(ChainMap, dict):
    pass

class Namespace(type):
    def __new__(meta, name, bases, dict):
        mod = ModuleType(name, dict.get("__doc__"))
        for key, obj in dict.items():
            if isinstance(obj, FunctionType):
                obj = meta.chained_function(meta, obj, mod)
            mod.__dict__[key] = obj
        return mod
    def chained_function(meta, func, mod):
        d = _NSChainedDict(mod.__dict__, func.__globals__)
        newfunc = FunctionType(func.__code__, d)
        newfunc.__doc__ = func.__doc__
        newfunc.__defaults__ = func.__defaults__
        newfunc.__kwdefaults__ = func.__kwdefaults__
        return newfunc


# === Test code ===

a = 999
b = 4

def spam(n=None):
    raise RuntimeError('no spam for you!')

class meals(metaclass=Namespace):
    a = 2
    def repeat(word, count):
        return ' '.join([word]*count)
    def ham(n=1):
        return repeat('ham', n)
    def spam(n=3):
        return repeat('spam', n)
    def breakfast():
        """Return yummy and nutritious breakfast"""
        template = "%s with a fried egg on toast and %s"
        return template % (spam(), spam(1))
    def lunch():
        """Return delicious and healthy lunch"""
        template = "cheese, tomato and %s sandwiches"
        return template % spam(a)
    def dinner():
        """Return tasty and wholesome dinner"""
        template = "roast %s garnished with %s"
        return template % (ham(), spam(b))


assert meals.breakfast() == 'spam spam spam with a fried egg on toast and spam'
assert meals.lunch() == 'cheese, tomato and spam spam sandwiches'
assert meals.dinner() == 'roast ham garnished with spam spam spam spam'

Some features of the namespace objects:

Although you define a namespace using the class keyword, the object created is actually a module. Being a module, the functions inside it are actual functions, not methods. That means you don't give them a self argument.

Because the functions are functions, and not methods, it is simple to use them while the namespace is being built. Here is a toy example:

>>> class toy(metaclass=Namespace):
...     def helper(x):
...             return 2**x
...     a = helper(1)
...     b = helper(8)
...
>>> toy.a
2
>>> toy.b
256
>>> toy.helper(5)
32

With classes, you normally have the choice of making the helper a function, in which case it doesn't work once the class is created, or a method, in which case it doesn't work until the class is created.

Inside the functions, the scope is:

  • local variables
  • variables in the namespace
  • global variables
  • builtins

So in the test example above, meals.lunch automatically sees meals.a without needing to prefix it with self or meals. But since meals.b does not exist, the global b is automatically used instead.

Some limitations:

  • This recipe requires the ChainMap mapping from Python 3.3. If you're using an older version, you may be able to use or adapt Recipe 305268 instead.

  • Inside the namespace functions, keywords global, nonlocal and del do not work as expected.

  • This recipe abuses the class keyword. It would be better to have dedicated syntax for creating a namespace within a module.

C++ uses the syntax:

namespace identifier
{
entities
}

which would correspond to hypothetical Python syntax:

namespace name:
    indented code block