a nice metaclass example (was Re: structs in python)
Alex Martelli
aleax at aleax.it
Sun Jul 7 11:49:16 EDT 2002
Alex Martelli wrote:
...
> amount of memory consumed don't interfere. __slots__ is OK, to save
> memory, when you have many instances with just a few data fields each
> (and the same field names in each instance), but it does admittedly
> demand more sophisticated implementation than Bunch, presumably via a
> metaclass.
Ah, forget the "presumably", here's a simple example. I've been looking
for good and simple custom metaclass examples for quite a while, after
all, so it would be silly to miss this occasion just because I SHOULD be
finishing the draft of Python in a Nutshell...!-).
import warnings
class metaMetaBunch(type):
"""
metaclass for new and improved "Bunch": implicitly defines
__slots__, __init__ and __repr__ from variables bound in class scope.
An instance of metaMetaBunch (a class whose metaclass is metaMetaBunch)
defines only class-scope variables (and possibly special methods, but
NOT __init__ and __repr__!). metaMetaBunch removes those variables from
class scope, snuggles them instead as items in a class-scope dict named
__dflts__, and puts in the class a __slots__ listing those variables'
names, an __init__ that takes as optional keyword arguments each of
them (using the values in __dflts__ as defaults for missing ones), and
a __repr__ that shows the repr of each attribute that differs from its
default value (the output of __repr__ can be passed to __eval__ to make
an equal instance, as per the usual convention in the matter).
"""
def __new__(cls, classname, bases, classdict):
""" Everything needs to be done in __new__, since type.__new__ is
where __slots__ are taken into account.
"""
# define as local functions the __init__ and __repr__ that we'll
# use in the new class
def __init__(self, **kw):
""" Simplistic __init__: first set all attributes to default
values, then override those explicitly passed in kw.
"""
for k in self.__dflts__: setattr(self, k, self.__dflts__[k])
for k in kw: setattr(self, k, kw[k])
def __repr__(self):
""" Clever __repr__: show only attributes that differ from the
respective default values, for compactness.
"""
rep = [ '%s=%r' % (k, getattr(self, k)) for k in self.__dflts__
if getattr(self, k) != self.__dflts__[k]
]
return '%s(%s)' % (classname, ', '.join(rep))
# build the newdict that we'll use as class-dict for the new class
newdict = { '__slots__':[], '__dflts__':{},
'__init__':__init__, '__repr__':__repr__, }
for k in classdict:
if k.startswith('__'):
# special methods &c: copy to newdict, warn about conflicts
if k in newdict:
warnings.warn("Can't set attr %r in bunch-class %r" % (
k, classname))
else:
newdict[k] = classdict[k]
else:
# class variables, store name in __slots__ and name and
# value as an item in __dflts__
newdict['__slots__'].append(k)
newdict['__dflts__'][k] = classdict[k]
# finally delegate the rest of the work to type.__new__
return type.__new__(cls, classname, bases, newdict)
class MetaBunch(object):
""" For convenience: inheriting from MetaBunch can be used to get
the new metaclass (same as defining __metaclass__ yourself).
"""
__metaclass__ = metaMetaBunch
# Example use: a meta-bunch class
class Point(MetaBunch):
""" A point has x and y coordinates, defaulting to 0.0, and a color,
defaulting to 'gray' -- and nothing more, except what Python and
the metaclass conspire to add, such as __init__ and __repr__
"""
x = 0.0
y = 0.0
color = 'gray'
# example uses of class Point
q = Point()
print q
p = Point(x=1.2, y=3.4)
print p
Apart from arbitrary ordering of the arguments, and representation of
floating point numbers being not all that nice, you'll see the "print"
statements emit basically the same constructors we just used.
The point is: if you use a lot of "struct-like" classes, metaMetaBunch
offers you a nice and convenient way to define them, with clean syntax
and reasonable default semantics (__init__ and __repr__). If your
application needs are slightly different, you can of course tweak this
pretty easily -- it IS, after all, a modest amount of pretty simple
lines of code. An instance of a metabunch class is quite memory-lean,
and using it should be just about as fast as using an instance of the
original Bunch class. I hope this provides a reasonable starting point
in the study of "what are Python 2.2 metaclasses good for"!
BTW, note that this example doesn't care about the bugfix in the
current CVS 2.3 wrt 2.2 regarding the mangling of slotnames that start
with '__' -- the metabunch class we build doesn't end up with any slotname
with two leading underscores, anyway, and if we have something like:
class thing(MetaBunch):
__var = 23
the class-scope variable name is already mangled to _thing__var by
the time our metaMetaBunch class gets to deal with it. Of course,
this doesn't necessarily make much sense for a class meant to have
no methods (!), but, hey, whatever floats your boat...
Alex
More information about the Python-list
mailing list