Reputation: 588
I have a series of classes all, inheriting from one of several possible parents which all share a base class. Each class has a class-scope Dict[str, object] parameters
which is based on the base class parameters
from copy import deepcopy
class BaseClass:
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(Baseclass):
parameters = {
**deepcopy(Baseclass.parameters),
'example_param2': ParamObject(name='example_param2', editable=False),
}
class ChildClass(MiddleClass):
parameters = {
**deepcopy(MiddleClass.parameters),
'example_param3': ParamObject(name='example_param3', editable=True),
}
This implementation does the job, but I find the **deepcopy(Baseclass.parameters),
line unsatisfying. These child classes are going to be edited over time by someone with only a basic understanding of coding so I want to make the code as simple and cut-and-pasteable as possible.
Is there anything I can call at class scope to get the equivilant of super().__class__
? I want the user to be able to change the base class from, say, MiddleClass
to MiddleClass2
without needing to remember to change the base class in multiple locations.
Upvotes: 3
Views: 611
Reputation: 2132
You can use Metaclasses, such as the other answers suggested, but I'm not sure how easier that would be for a new programmer to understand.
You can access the classes a class inherithed from by calling __bases__
on the class. Sadly though, on python you cannot reference the class you're creating on the class body, meaning you can't do:
class A:
bases = A.__bases__ # Invalid code, A is not defined yet
If you want to do this, while mantaining parameters static, you can do this after the class definition, and it will have the exact behaviour you're looking for, although a bit ugly:
class BaseClass:
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(Baseclass):
pass
MidleClass.parameters = {
**deepcopy(MidleClass.__bases__[0].parameters),
'example_param2': ParamObject(name='example_param2', editable=False),
}
Now, to improve things a bit, if your parameters
is really always a 'Dict[str, object]', you don't need to do a deepcopy, since using the **
acts in a functional way. You can also handle multiple parent classes using the __bases__
value, to ensure every value is being captured. So, if I were to do this, I would do something like this:
def base_parameters(cls):
parameters = {}
for base in cls.__bases__:
parameters.update(base.parameters)
return parameters
class BaseClass:
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(BaseClass):
pass
MiddleClass.parameters = {
**base_parameters(MiddleClass),
'example_param2': ParamObject(name='example_param2', editable=False),
}
class ChildClass(MiddleClass):
pass
ChildClass.parameters = {
**base_parameters(ChildClass),
'example_param3': ParamObject(name='example_param3', editable=True),
}
Upvotes: 0
Reputation: 531075
You can use __init_subclass__
to add parameters from parent classes directly to the explicitly defined parameters
attribute, at the cost of the insertion order being reversed (should that matter).
class ParameterBase:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for pc in cls.__bases__:
d = getattr(pc, 'parameters', {})
cls.parameters.update(d)
class BaseClass(ParameterBase):
parameters = {
'example_param1': ParamObject(name='example_param1', editable=True),
}
class MiddleClass(BaseClass):
parameters = {
'example_param2': ParamObject(name='example_param2', editable=False),
}
class ChildClass(MiddleClass):
parameters = {
'example_param3': ParamObject(name='example_param3', editable=True),
}
Upvotes: 5
Reputation: 23624
You could do this with a metaclass. Check all of the classes in the inheritance tree to see if they have the parameter
s field. If they do, then merge them together and set the property on the class instance.
class ParamMeta(type):
def __init__(cls, name, bases, dct):
class_types = [cls] + list(bases)
parameters = {}
for class_type in class_types:
if hasattr(class_type, "parameters"):
parameters.update(class_type.parameters)
cls.parameters = parameters
super().__init__(name, bases, dct)
Example:
class Foo(metaclass=ParamMeta):
parameters = {"a": "b"}
class Bar(Foo):
pass
class Fizz(Bar):
parameters = {"c": "d"}
print(Foo.parameters)
print(Bar.parameters)
print(Fizz.parameters)
Outputs:
{'a': 'b'}
{'a': 'b'}
{'c': 'd', 'a': 'b'}
Upvotes: 2
Reputation: 44838
If your class inherits from only one class, you can use a metaclass:
class Meta(type):
def __new__(cls, name, parents, namespace):
namespace['parameters'] = {
**deepcopy(parents[0].parameters),
**namespace['parameters']
}
return super().__new__(cls, name, parents, namespace)
class MiddleClass(Baseclass, metaclass=Meta):
parameters = {
'example_param2': ParamObject(name='example_param2', editable=False),
}
This is of course a simplified version that doesn't handle multiple parent classes, for example.
Upvotes: 0