Say if there's a class attribute that is calculated depending on another class attribute:
class ClassA(object):
attr1=5
attr2=attr1+10
>>ClassA.attr2
15
And then in a child class I want to update the first class attribute to cause a change in the second one, without re-defining the second. e.g:
class ClassB(ClassA):
attr1=10
What I would like to happen is:
>>ClassB.attr2
20
However this isn't the case since attr2 gets calculated before attr1 is re-defined. Is there a way to obtain this behaviour without redefining the second attribute?
(My particular use case is defining WTForms with Field attributes (format, choices, etc) that are derived from the Form class attributes, so I can subclass a base Form and change these attributes without having to re-define the entire Field.)
There's no way to do this directly.
For instance attributes, rather than class attributes, you can simulate it pretty easily by using @property
. Either calculate attr2
on the fly every time you ask for it:
class Spam:
def __init__(self, a):
self.a = a
@property
def twoa(self):
return 2*self.a
(possibly adding a cache if calculation is expensive), or recalculate attr2
whenever anyone modifies attr1
:
class Spam:
def __init__(self, a):
self.a = a
@property
def a(self):
return self._a
@a.setter
def a(self):
self._a = a
self.twoa = 2*a
That only works because property
creates a descriptor that you store in the class object, rather than the instance, so when someone looks up twoa
(in the first example) or a
(in the second) on a Spam
instance, it falls back to finding the descriptor on the class.
If you want the first one for a class attribute, it works as-is when looking up values on instances, but not on the class itself (which, unfortunately, is what you're doing in your examples).
And if you need the second one, it won't work at all.
For example:
>>> class Spam:
... a = 10
... @property
... def twoa(self):
... return 2*self.a
>>> spam = Spam()
>>> spam.twoa
20
>>> Spam.twoa
<property at 0x12345678>
>>> class Eggs(Spam):
... a = 5
>>> eggs = Eggs()
>>> eggs.twoa
10
>>> Eggs.twoa
<property at 0x12345678>
If that's not a problem, great. But if it is, you need to put the descriptor on the class's class—that is, a metaclass:
class MetaSpam(type):
@property
def twoa(cls):
return 2 * cls.a
class Spam(metaclass=MetaSpam):
a = 2
class Eggs(Spam):
a = 3
For a simple case like twoa
(or your attr2
), dragging in a metaclass is horrible overkill.
But for a case where there's a good chance you're already dragging in custom metaclasses, like a complex forms-and-fields system, it might be appropriate.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With