Class dictionary inheritance with metaclasses
I am setting a property of a class fields
using a metaclass:
class MyMeta(type):
def __new__(mcs, name, bases, clsdict):
clsdict['fields'] = {k: v
for k, v in clsdict.items()
if <my_condition>}
return super(MyMeta, mcs).__new__(mcs, name, bases, clsdict)
class MyBaseClass(metaclass=MyMeta):
fields = {}
The following instance produces the expected results:
class SubClass(MyBaseClass):
param1 = 1 # meets <my_condition>
>>> SubClass.fields
{param1: 1}
But if I now subclass SubClass
, fields
empty:
class SubSubClass(SubClass):
pass
>>> SubSubClass.fields
{}
How can I update the classdict of all classes in the inheritance hierarchy so that the variable is fields
updated from the base classes?
source to share
You need to store the fields
superclasses somehow , for example by iterating over "bases" and using them fields
as a starting point:
class MyMeta(type):
def __new__(mcs, name, bases, clsdict):
if 'fields' not in clsdict:
clsdict['fields'] = {}
# Initialize "fields" from base classes
for base in bases:
try:
clsdict['fields'].update(base.fields)
except AttributeError:
pass
# Fill in new fields (I included a "trivial" condition here, just use yours instead.)
clsdict['fields'].update({k: v for k, v in clsdict.items() if k.startswith('param')})
return super(MyMeta, mcs).__new__(mcs, name, bases, clsdict)
And it works for SubClass
and SubSubClass
:
>>> SubClass.fields
{'param1': 1}
>>> SubSubClass.fields
{'param1': 1}
source to share
I suggest turning fields
into a property descriptor that fetches all content _fields
from parent classes. This way, you can also more easily customize what happens when name conflicts, etc.
class MyMeta(type):
def __new__(mcs, name, bases, clsdict):
# change fields to _fields
clsdict['_fields'] = {k: v
for k, v in clsdict.items()
if <my_condition>}
return super(MyMeta, mcs).__new__(mcs, name, bases, clsdict)
@property
def fields(cls):
# reversed makes most recent key value override parent values
return {k:v
for c in reversed(cls.__mro__)
for k,v in getattr(c, '_fields', {}).items() }
Using:
class MyBaseClass(metaclass=MyMeta):
fields = {}
class SubClass(MyBaseClass):
param1 = 1
>>> SubClass.fields
{param1: 1}
class SubSubClass(SubClass):
pass
>>> SubSubClass.fields
{param1: 1} # success
Usage now SomeChildClass.fields
always refers to the metaclass property. The third argument getattr
allows classes with no attribute _fields
(for example object
) to fail.
Using a descriptor also has the advantage that a child class cannot accidentally override an attribute fields
:
>>> SubSubClass.fields = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
You can also create a setter if needed and use it in the method __init__
(i.e. revert to using fields
instead _fields
) so that the rest of the class is implementation agnostic:
@fields.setter
def fields(cls, mapping):
try:
cls._fields.update(**mapping)
except AttributeError:
cls._fields = dict(**mapping)
source to share