Reputation: 2956
I have a class like:
class Parent(object):
def __init__(self, a1 = None, a2 = None):
someInitialization()
self.a1 = a1
self.a2 = a2
def coolMethod(self):
return dosomething(self.a2)
Let's say that
EDIT my actual constraints are not really so strict, but I ask this question also for understanding better the class attributes, methods, etc..
I am using the simplest solution without initialization inheritance:
class Child(Parent):
_propertiesCache = {'a1':None,'a2':None}
def _initProperty(self, propertyName):
value = self._propertiesCache[propertyName]
if value is None:
self._propertiesCache[propertyName]=expensiveFunction(self.b, propertyName)
return self._propertiesCache[propertyName]
@property
def a1(self):
return self._initProperty('a1')
@property
def a2(self):
return self._initProperty('a2')
def __init__(self,b):
self.b = b
someInitialization()
Is there a way to properly call the initialization of the parent class? If I use super(Child,self).__init__()
I get AttributeError: can't set attribute
.
I tried to override __new__
but I am always missing something
class Child(Parent):
_propertiesCache = {'a1':None,'a2':None}
@classmethod
def _initProperty(cls, propertyName):
value = cls._propertiesCache[propertyName]
if value is None:
cls._propertiesCache[propertyName] = someTimeConsumingFunctionThatIDontWantToCallAtInitialization(propertyName)
return cls._propertiesCache[propertyName]
@property
def a1(self):
return self._initProperty('a1')
@property
def a2(self):
return self._initProperty('a2')
def __new__(cls):
super(Child,cls).__new__(cls)
return cls
def __init__(self,b):
self.b = b
super(Child,self).__init__(a1=self.a1, a2=self.a2)
Gives me:
>>c = Child();c.a1
<property at 0x3aa4228>
>>c.a1.fget(c)
"ValueOfa1"
Upvotes: 7
Views: 9763
Reputation: 1553
I believe that much more pythonic and in line with the OOP convention is not to change the Parent
class - it does not have to anticipate how its children override its arguments and behavior.
In order for Child
to use a1
and a2
as properties it has to implement both the getter (@property
) and dummy setter (@a1.setter
).
The code below assumes that a1
could not be modified by user (so the setter is implemented as a dummy and __init__
of Parent is called with arbitrary value). Property a2
has slightly more complicated behavior: it could be initialized or modified by user, but if its value is not valid (e.g. negative here) - it is overridden by internal logic.
class Child(Parent):
def __init__(self):
super().__init__(None, -42)
@property
def a1(self):
return 42
@a1.setter
def a1(self, value):
""" Do nothing. Implemented to prevent Attribute Error """
@property
def a2(self):
if getattr(self, '_a2', -1) < 0:
self._a2 = self._initProperty('a2')
return self._a2
@a2.setter
def a2(self, value):
self._a2 = value
P.S. You could reduce the dummy setter to just one line:
class Child(Parent):
# ...
a1 = a1.setter(lambda self, value: None)
Upvotes: 1
Reputation: 21464
instead of calling your method _initProperty
call it __getattr__
so that it will be called every time the attribute is not found in the normal places it should be stored (the attribute dictionary, class dictionary etc.) then the first time the attribute is tried to be accessed it gets initialized.
Be sure to not set them in the Parent initialization, maybe only set them if they are not None:
class Parent:
def __init__(self,a1=None,a2=None):
if a1 is not None:
self.a1 = a1
if a2 is not None:
self.a2 = a2
As well to stay consistent with errors you will want to raise an AttributeError
if the attribute doesn't exist instead of letting the KeyError
go through, and maybe add a reference to the value in the regular attribute dict so that it doesn't need to run the __getattr__
every time:
_propertiesCache = {'a1':None,'a2':None}
def __getattr__(self, propertyName):
if propertyName not in self._propertiesCache:
raise AttributeError(propertyName)
value = self._propertiesCache[propertyName]
if value is None:
value = self._propertiesCache[propertyName]=expensiveFunction(self.b, propertyName)
setattr(self,propertyName,value)
return value
Any way you implement this you need to make sure:
The attribute is not set until the first time they are used (at which point __getattr__
gets used)
Upvotes: 4