I have a bunch of classes that get persisted in a ZODB. All classes have to share some indexing features, but also all of them define a lot of properties. The properties are defined on class level with descriptors, to be able to intercept writing to them for validation and check for duplicates in the DB. For this, every class has a list of the automatically maintained properties, _properties
. They also hold _indices
and some other features, which I don't want to copy on my own to every single Element-class (about 10 or so).
Consider the following code:
class BaseElement(object):
_properties = None
class ElementFlavour1(BaseElement):
_properties = 'a', 'b', 'c'
....
class ElementFlavourN(BaseElement):
_properties = 'e', 'f', 'g'
for item in locals():
if issubclass(item, BaseElement):
item._v_properties_set = set(item._properties) if item._properties else set()
So, it basically does what it should do: I need to iterate the _properties
linearly, but I also want to do quick lookups, if some property is present in a class. Nevertheless, I don't want to repeat myself and specify this set for every single flavour of BaseElement
explicitly.
The current solution works, but I consider it as an ugly hack and would like to get rid of it. E.g. subclasses of BaseElement
in other modules wouldn't get their properties set right. Playing around with __new__
also seems to be the wrong way, as it doesn't intercept the class construction, but the instantiation. So, how to do that properly?
P.S. I'm aware of OrderedDict, but this is not what I'm looking for. I'd like to hook into the class creation process in a clean way and append arbitrary functionality there.
You could do this with a metaclass:
class BaseType(type):
def __init__(cls, name, bases, clsdict):
super(BaseType, cls).__init__(name, bases, clsdict)
setattr(cls,'_v_properties_set',
set(cls._properties) if cls._properties else set())
class BaseElement(object):
__metaclass__ = BaseType
_properties = None
class ElementFlavour1(BaseElement):
_properties = 'a', 'b', 'c'
class ElementFlavourN(BaseElement):
_properties = 'e', 'f', 'g'
print(ElementFlavourN._v_properties_set)
# set(['e', 'g', 'f'])
You could also do this with a class decorator, but then you'd have to decorate each subclass of BaseElement
individually. The metaclass is inherited, so you only have to define it once.