71 lines
3.0 KiB
Python
71 lines
3.0 KiB
Python
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(metaclass=metaMetaBunch):
|
|
""" For convenience: inheriting from MetaBunch can be used to get
|
|
the new metaclass (same as defining __metaclass__ yourself).
|
|
"""
|
|
__metaclass__ = metaMetaBunch
|