简体   繁体   中英

Best practice for defining a class that computes attributes in order when initialized

I would like to define a class that does something like:

Class computer():
    def __init__(self, x):
        # compute first the 'helper' properties
        self.prop1 = self.compute_prop1(x)
        self.prop2 = self.compute_prop2(x)
        # then compute the property that depends on 'helpers'
        self.prop3 = self.compute_prop3(x)

    def compute_prop1(self, x):
        return x
    def compute_prop2(self, x):
        return x*x
    def compute_prop3(self, x):
        return self.prop1 + self.prop2

Then, when I initialize an instance, I get all properties computed in order (first helpers, then everything depending on helpers later):

>>> computer = Computer(3)
>>> computer.__dict__
{'prop1': 3, 'prop2': 9, 'prop3': 12}

However, I think there is a better practice of writing this code, for example using decorators. Could you please give me some hints? Thank you!

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.

I think the following would be the easiest way to avoid redundancy

class computer():
    def __init__(self, x):
        self.prop_dict = self.compute_prop_dict(x)

    def compute_prop_dict(self, x):
        prop1 = x
        prop2 = x*x
        return {'prop1': prop1, 'prop2': prop2, 'prop3': prop1 + prop2}

So anything that would come after instantiation could have access to these helpers via the prop_dict

But as said by Brian as a comment this order is just a language specification for Python 3.7

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM