2011-06-22
A basic Namespace metaclass for Python
Suppose that you want to conveniently abuse classes as namespaces. For me, this means avoiding having to splatter
@staticmethod
all over my class definitions; it's both repetitive
make-work and distracting when I'm looking at the source code.
Clearly the solution to this problem is a metaclass to do all of the work for me. Here's one, which is deliberately somewhat simple:
from types import FunctionType
class NSMeta(type): def __new__(meta, cname, bases, cdict): ndict = {} for name, aval in cdict.items(): if type(aval) == FunctionType: aval = staticmethod(aval) ndict[name] = aval return type.__new__(meta, cname, \ bases, ndict) # Inherit from this class: class Namespace(object): __metaclass__ = NSMeta # An example: class uint16(Namespace): def encode(val): return ... def decode(data): return ...
(To fully exhibit my twitch about aesthetics and not repeating myself,
I set up the Namespace
class purely so that 'namespace' classes would
not have to add an extra line to set __metaclass__
. Inheriting from
Namespace
is free, in terms of code layout, since they need to
inherit from something.)
A really superintelligent metaclass would only remap
functions and would peek at what the first argument of every function
was called. If it was 'self', the function would be left alone (and
would thus become a method function); if it was 'cls' (or your preferred
equivalent), the function would be made into a classmethod; otherwise,
the function would be made into a staticmethod.
Writing such a superintelligent metaclass is left as an exercise for the reader.
Update: code in haste, repent slightly later. I have realized that
this metaclass should at least only apply staticmethod
to functions,
since there are sensible uses for non-function things in a namespace
(such as constants). So I've fixed it to do this right.
Abusing Python classes as namespaces
Suppose, not entirely hypothetically, that you are dealing with binary structures and so have a bunch of functions to encode Python values to various different sorts of binary fields and decode those fields back to Python values. These functions clearly come in pairs.
There are various ways of organizing these functions; for
example, you can just give them names like encode_uint16
and
decode_uint16
. One attractive way would be to group the pairs of
functions together in namespaces, for example as uint16.encode
and
uint16.decode
; this becomes even more useful if you add more per
field type functions (for example, a validator function).
Unfortunately, Python does not have general namespace support. There is no convenient way to open up a new namespace and start defining things in it; all of the ways of creating namespaces come with various sorts of baggage. The closest Python comes to pure namespaces without side effects is modules, but you have to put them in separate files and I think that this gets ugly and hard to follow because the modules (and thus the files) are just too small.
(In theory one answer is to add some methods to your value
types. However, even if you have modifiable classes for your value
types, it may be inappropriate to add .encode
and .decode
methods to them because the same value type may have multiple different
possible encodings. And if you're using basic Python values (ints,
strings, etc), you don't even have modifiable classes to add methods
to.)
The only real way to conveniently define multiple namespaces in a single
file is to make them classes. But not conventional classes. Classes
used purely as namespaces will never be instantiated; with no instances
and no per-instance state, none of the 'methods' will actually ever use
self
and so there is no point in having them called with it. In short
we want a class that has only staticmethod
functions, because that
makes them into regular plain old functions:
class uint16(object): @staticmethod def encode(val): return ... @staticmethod def decode(data): return ...
(This class deliberately breaks the usual Python naming convention for classes, because it's nothing like a normal class.)
In some cases it may be useful to have per-class data and some sort of class hierarchy in order to reuse code. That's the time for @classmethod.
PS: having tried this approach out in some of my code, I confess that I'm not sure I like it. The problem for me is an aesthetic one; I think I got the 'namespace' names wrong (I made them too class-like) and I'm not sure I like the @staticmethod's that are sprayed all over the place.