Incremental computation with __setattr__()

We can create classes which use __setattr__() to detect changes in attribute values. This can lead to incremental computation. The idea is to build derived values after initial attribute values have been set.

Note the complexity of having two senses of attribute setting.

  • The client's view: An attribute can be set and other derived values may be computed. In this case, a sophisticated __setattr__() is used.
  • The internal view: Setting an attribute must not result in any additional computation. If additional computation is done, this leads to an infinite recursion of setting attributes and computing derived values from those attributes. In this case, the fundamental __setattr__() method of the superclass must be used.

This distinction is important and easy to overlook. Here's a class that both sets attributes and computes derived attributes in the __setattr__() method:

class RTD_Dynamic:
def __init__(self) -> None:
self.rate : float
self.time : float
self.distance : float

super().__setattr__('rate', None)
super().__setattr__('time', None)
super().__setattr__('distance', None)

def __repr__(self) -> str:
clauses = []
if self.rate:
clauses.append(f"rate={self.rate}")
if self.time:
clauses.append(f"time={self.time}")
if self.distance:
clauses.append(f"distance={self.distance}")
return (
f"{self.__class__.__name__}"
f"({', '.join(clauses)})"
)

def __setattr__(self, name: str, value: float) -> None:
if name == 'rate':
super().__setattr__('rate', value)
elif name == 'time':
super().__setattr__('time', value)
elif name == 'distance':
super().__setattr__('distance', value)

if self.rate and self.time:
super().__setattr__('distance', self.rate * self.time)
elif self.rate and self.distance:
super().__setattr__('time', self.distance / self.rate)
elif self.time and self.distance:
super().__setattr__('rate', self.distance / self.time)

The __init__() method uses the __setattr__() superclass to set default attribute values without starting a recursive computation. The instance variables are named with type hints, but no assignment is performed.

The RTD_Dynamic class provides a __setattr__() method that will set an attribute. If enough values are present, it will also compute derived values. The internal use of super().__setattr__() specifically avoids any additional computations from being done by using the object superclass attribute setting methods.

Here's an example of using this class:

>>> rtd = RTD_Dynamic()
>>> rtd.time = 9.5
>>> rtd
RTD_Dynamic(time=9.5)
>>> rtd.rate = 6.25
>>> rtd
RTD_Dynamic(rate=6.25, time=9.5, distance=59.375)
>>> rtd.distance
59.375
Note that we can't set attribute values inside some methods of this class using simple self.name = syntax.

Let's imagine we tried to write the following line of code inside the __setattr__() method of this class definition:

self.distance = self.rate*self.time 

If we were to write the preceding code snippet, we'd have infinite recursion in the __setattr__() method. In the self.distance=x line, this is implemented as self.__setattr__('distance', x). If a line such as self.distance=x occurs within the body of __setattr__(), it means __setattr__() will have to be used while trying to implement attribute settings. The __setattr__() superclass doesn't do any additional work and is free from recursive entanglements with itself.

It's also important to note that once all three values are set, changing an attribute won't simply recompute the other two attributes. The rules for computation are based on an explicit assumption that one attribute is missing and the other two are available.

To properly recompute values, we need to make two changes: 1) set the desired attribute to None, and 2) provide a value to force a recomputation.

We can't simply set a new value for rate and compute a new value for time while leaving distance unchanged. To tweak this model, we need to both clear one variable and set a new value for another variable:

>>> rtd.time = None
>>> rtd.rate = 6.125
>>> rtd
RTD_Dynamic(rate=6.125, time=9.5, distance=58.1875)

Here, we cleared time and changed rate to get a new solution for time using the previously established value for distance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset