Here's your class using properties instead (with an added method for returning each property):
Class PropertyComputer:
def __init__(self, x):
self._x = x
@property
def prop1(self):
return self._x
@property
def prop2(self):
return self._x * self._x
@property
def prop3(self):
return self.prop1 + self.prop2
def get_props(self):
return self.prop1, self.prop2, self.prop3
Design-wise, I believe this is better because:
- storing
x
as an instance variable makes more sense: the point of using objects is to avoid having to pass variables around, especially those that the object itself can keep track of;
- the attribute assignment and its corresponding calculation are bundled together in each property-decorated method: we'll never have to think whether the problem is in the init method (where you define the attribute) or in the compute method (where the logic for the attribute's calculation is laid out).
Note that the concept of "first calculate helpers, then the properties depending on them" does not really apply to this code: we only need to evaluate prop3
if/when we actually need it. If we never access it, we never need to compute it.
A "bad" side-effect of using properties, compared to your example, is that these properties are not "stored" anywhere (hence why I added the last method):
c = PropertyComputer(x=2)
c.__dict__ # outputs {'_x': 2}
Also note that, using decorators, the attributes are calculated on-the-fly whenever you access them, instead of just once in the init method. In this manner, property-decorated methods work like methods, but are accessed like attributes (it's the whole point of using them):
c = PropertyComputer(x=2)
c.prop1 # outputs 2
c._x = 10
c.prop1 # outputs 10
As a side note, you can use functools.cached_property
to cache the evaluation of one of these properties, in case it's computationally expensive.