Reputation: 311
I have an attribute in a class that I want to calculate from other attributes.
I want this attribute to be private, so I would like to make it a property.
I understand that a property still needs to be called in the init block of the class, however i didn't find an elegant way to write that.
Some ugly code that does what I need:
class ABC:
def __init__(self, t=0):
self.attrA = t
self._attrB = None
@property
def attrB(self):
return self._attrB
@attrB.setter
def attrB(self, value):
self._attrB = 2*self.attrA
obj = ABC(10)
print(obj.attrB)
Question: how can I avoid declaring attrB in init with a dummy 'None' value?
Edit to previous post:
Ideally I would like attrB to really be a 'stored' attribute (not just a property getter), so that it is not recalculated every time it is called: in my usecase attrA and attrB are long lists so recalculating attrB each time it is called is a no-go
I understand caching the property call is also an option, but is there not a simpler way by having a real attribute?
Upvotes: 4
Views: 2377
Reputation: 4964
About the edited part of your post:
An atrrB
property is an attribute. By making it a property, you just add code that runs when this attribute is accessed for read or write, or both.
A property is one of the possible types of a descriptor
Caching an expensive calculation of a property is a good solution.
If you are starting with Python, descriptors is an advanced feature. However using properties or even caching is quite straightforward.
Upvotes: 0
Reputation: 71464
You don't need a self._attrB
at all, or an attrB.setter
, since your attrB
getter can return whatever you want the value of attrB
to be (in this case it's twice the value of attrA
):
class ABC:
def __init__(self, t=0):
self.attrA = t
@property
def attrB(self):
return 2 * self.attrA
obj = ABC(10)
print(obj.attrB)
A good clue that your attrB
setter was unnecessary is that it ignored the value
that you passed to it! It's simpler to just put this logic in the getter since it's not dependent on any data that isn't already stored in the class.
(edit) If the logic you use to generate attrB
is more complicated, you could cache it like this:
from functools import lru_cache
class ABC:
def __init__(self, t=0):
self.attrA = t
@property
def attrB(self):
return self._attrB(self.attrA)
@lru_cache
def _attrB(self, attrA):
return 2 * self.attrA
The @lru_cache
will cache the return value of _attrB
based on the value of attrA
, so it will recompute if attrA
has changed, and otherwise serve up the value from the cache.
If you don't want attrB
to change when attrA
changes, then you can simply use cached_property
, which computes the value the first time you access it and thereafter uses a cached copy:
from functools import cached_property
class ABC:
def __init__(self, t=0):
self.attrA = t
@cached_property
def attrB(self):
return 2 * self.attrA
and obviously if you don't want attrA
itself to change after it's initialized, you can make it a private variable.
Upvotes: 6