Reputation: 4344
I am looking for best practices on setting one instance attribute that references another instance attribute after the class has been instantiated.
For example:
class Foo:
def __init__(self):
self.a = 1
self.b = self.a + 1
>>> obj_foo = Foo()
>>> obj_foo.a
1
>>> obj_foo.b
2
>>> obj_foo.a = 5
>>> obj_foo.a
5
>>> obj_foo.b
2 # I want this to be 6
Is this bad practice for one instance attribute to reference another?
I can see how implementing a method to check for and update dependent instance attributes, but this seems like a lot of overhead/hacky. Any assistance is greatly appreciated!
Upvotes: 2
Views: 72
Reputation: 114320
It seems like you don't actually want to store the value of b
at all, but instead want to generate it based on the value of a
dynamically. Luckily, there's a property
class/decorator that you can use just for this purpose:
class Foo:
def __init__(self, a=1):
self.a = a
@property
def b(self):
return self.a + 1
This will create a read-only property b
that will behave just like a normal attribute when you access it as foo.b
, but will a because it is a descriptor. It will re-compute the value based on whatever foo.a
is set to.
Your fears about calling a method to do the computation every time are not entirely unjustified. Using the .
operator already performs some fairly expensive lookups, so your toy case is fine as shown above. But you will often run into cases that require something more than just adding 1 to the argument. In that case, you'll want to use something like caching to speed things up. For example, you could make a
into a settable property. Whenever the value of a
is updated, you can "invalidate" b
somehow, like setting a flag, or just assigning None
to the cached value. Now, your expensive computation only runs when necessary:
class Foo:
def __init__(self, a=1):
self._a = a
@property
def a(self):
return self._a
@a.setter
def a(self, value):
self._a = value
self._b = None
@property
def b(self):
if self._b is None:
# Placeholder for expensive computation here
self._b = self._a + 1
return self._b
In this example, setting self.a = a
in __init__
will trigger the setter for the property foo.a
, ensuring that the attribute foo._b
always exists.
Upvotes: 6