crixus
crixus

Reputation: 311

How to initialize attributes calculated from other attributes in python classes

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:

Upvotes: 4

Views: 2377

Answers (2)

progmatico
progmatico

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

Samwise
Samwise

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

Related Questions