"Namespaces are one honking great idea -- let's do more of those!" -- The Zen of Python
For when you want a simple, easy namespace, but you don't want it cluttered up with Python's object machinery.
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | """namespace module"""
__all__ = ("Namespace", "as_namespace")
from collections import Mapping, Sequence
class _Dummy: ...
CLASS_ATTRS = dir(_Dummy)
del _Dummy
class Namespace(dict):
"""A dict subclass that exposes its items as attributes.
Warning: Namespace instances do not have direct access to the
dict methods.
"""
def __init__(self, obj={}):
super().__init__(obj)
def __dir__(self):
return tuple(self)
def __repr__(self):
return "%s(%s)" % (type(self).__name__, super().__repr__())
def __getattribute__(self, name):
try:
return self[name]
except KeyError:
msg = "'%s' object has no attribute '%s'"
raise AttributeError(msg % (type(self).__name__, name))
def __setattr__(self, name, value):
self[name] = value
def __delattr__(self, name):
del self[name]
#------------------------
# "copy constructors"
@classmethod
def from_object(cls, obj, names=None):
if names is None:
names = dir(obj)
ns = {name:getattr(obj, name) for name in names}
return cls(ns)
@classmethod
def from_mapping(cls, ns, names=None):
if names:
ns = {name:ns[name] for name in names}
return cls(ns)
@classmethod
def from_sequence(cls, seq, names=None):
if names:
seq = {name:val for name, val in seq if name in names}
return cls(seq)
#------------------------
# static methods
@staticmethod
def hasattr(ns, name):
try:
object.__getattribute__(ns, name)
except AttributeError:
return False
return True
@staticmethod
def getattr(ns, name):
return object.__getattribute__(ns, name)
@staticmethod
def setattr(ns, name, value):
return object.__setattr__(ns, name, value)
@staticmethod
def delattr(ns, name):
return object.__delattr__(ns, name)
def as_namespace(obj, names=None):
# functions
if isinstance(obj, type(as_namespace)):
obj = obj()
# special cases
if isinstance(obj, type):
names = (name for name in dir(obj) if name not in CLASS_ATTRS)
return Namespace.from_object(obj, names)
if isinstance(obj, Mapping):
return Namespace.from_mapping(obj, names)
if isinstance(obj, Sequence):
return Namespace.from_sequence(obj, names)
# default
return Namespace.from_object(obj, names)
|
About Object Namespaces
Every object in python has a namespace, the mapping of attributes to values for that object; and the "dot" notation (obj.attr
) gives you direct access to those attributes. This is facilitated through the attribute access mechanism (__getattribute__()
, et al.).
The list of names in an object's namespace are exposed by its __dir__()
special method, effectively making it the "object namespace protocol". To pull that list from the object, you use dir(obj)
.
External Namespaces
Some objects also represent separate secondary namespaces. We can call these "external" namespaces, in contrast to the "internal" object namespace. Mappings (like dict) and sequences (like list or str) expose their external namespace through indexing operators.
For modules and classes the external namespace is more abstract and gets mixed in with the internal one. Just like with the internal namespace, the objects in the external namespace for modules and classes are exposed through attribute access.
The Problem
If you want a quick-and-easy attribute-based namespace currently, you have a few simple options:
class ns:
x = 1
y = 2
ns.x
# prints 1
ns.z = 2
# or
class Object: ...
ns = Object()
ns.x = 1
ns.y = 2
These define an external namespace containing the names "x" and "y". That's just what you wanted. However, ns
is also cluttered up with all the normal object attributes:
dir(ns)
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y']
A Solution
This recipe demonstrates a way to have an external namespace accessible through attribute access (like normal objects do), but still keep it separate from the internal one (like dicts do). The trade-off is that the internal namespace is no longer accessible through attribute access.
The trick is to use Python's awesome flexibility to give you that simple namespace without all the normal object attributes getting in the way. In the recipe, the Namespace
class is a dict subclass that exposes its keys as attributes. The as_namespace()
function converts an object into a new Namespace
object. It's especially useful as a class decorator.
Examples
spam = {'x': 1, 'y': 2}
spam = Namespace(spam)
spam
# Namespace({'x': 1, 'y': 2})
dir(spam)
# ['x', 'y']
spam.x
# 1
spam.x = 3
spam.x
# 3
@as_namespace
class spam:
x = 1
y = 2
spam
# Namespace({'x': 1, 'y': 2})
Namespace Instances Aren't Quite Dictionaries
Even though Namespace
is a subclass of dict, none of dict's instance attributes are available on Namespace
objects. This is due to the use of __getattribute__()
. This does not impact the use of special methods by operators and builtins since those are looked-up on the class and not the instance.
Just to clarify, here are the methods you might normally use on a dict instance that are not available on Namespace
instances:
clear, copy, fromkeys, get, items, keys, pop, popitem, setdefault, update, values
The special methods are likewise hidden. However, you can look up any of the attributes on the class to access them. Here are three examples of how you could do so:
dict.copy(ns)
type(ns).get(ns, "x")
Namespace.__getitem__(ns, "x")
Also, the Namespace
class has some static methods to make it easier to access the internal namespace of a Namespace
instance.
Warning: dict(ns)
doesn't work for Namespace
objects. Instead, use the dict unpacking syntax: dict(**ns)
. The dict constructor looks for the keys
attribute on the passed object to see if it is a mapping (__getattribute__()
precludes that here).
Student: Could this be used to store metadata?
Anywhere you could use a dict, you could use a Namespace. The difference is that the Namespace does not directly expose its __dict__ or use the normal name lookup. To address that, the Namespace class exposes several static methods.
Oh, I forgot ... key access is actually posssible. Since I cannot edit my post, I created a recipe of my own at http://code.activestate.com/recipes/578122-abusing-modules-as-namespaces/
So you can delete my previous comment.
I assume you meant
class _Dummy: pass
in line 7 notclass _Dummy: ...
.... is a literal for Ellipsis. In Python 3 you can use ... anywhere. In this case it would be the same as putting a string literal there, an integer, or any other expression.
In Python 3.2, calling
dir()
on a Namespace instance fails.Changing line 24 to return a
list
instead fixes this.--ap