Wandering Thoughts archives

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.

python/NamespaceMetaclass written at 01:51:27; Add Comment

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.

python/ClassesAsNamespaces written at 01:28:34; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.