- Mastering Objectoriented Python
- Steven F. Lott
- 459字
- 2021-11-12 16:25:13
Eagerly computed attributes
We can define an object where attributes are computed eagerly as soon as possible after a value is set. This object optimizes access by doing a computation once and leaving the result to be used multiple times.
We're able to define a number of property setters to do this. However, a lot of property setters, each of which compute a number of attributes, can get wordy for a complex calculation.
We can centralize the attribute processing. In the following example, we'll use a few tweaks to extend Python's internal dict
type. The advantage of extending dict
is that it works well with the format()
method of a string. Also, we don't have to worry much about setting extra attribute values that are otherwise ignored.
We'd like something that looks like the following code:
>>> RateTimeDistance( rate=5.2, time=9.5 ) {'distance': 49.4, 'time': 9.5, 'rate': 5.2} >>> RateTimeDistance( distance=48.5, rate=6.1 ) {'distance': 48.5, 'time': 7.950819672131148, 'rate': 6.1}
We can set the values in this RateTimeDistance
object. Additional attributes are computed as soon as sufficient data is present. We can do this either all at once, as shown earlier, or in stages, as shown in the following code:
>>> rtd= RateTimeDistance() >>> rtd.time= 9.5 >>> rtd {'time': 9.5} >>> rtd.rate= 6.24 >>> rtd {'distance': 59.28, 'time': 9.5, 'rate': 6.24}
The following is the extension to the built-in dict
. We've extended the essential mapping that dict
implements to compute a missing attribute:
class RateTimeDistance( dict ): def __init__( self, *args, **kw ): super().__init__( *args, **kw ) self._solve() def __getattr__( self, name ): return self.get(name,None) def __setattr__( self, name, value ): self[name]= value self._solve() def __dir__( self ): return list(self.keys()) def _solve(self): if self.rate is not None and self.time is not None: self['distance'] = self.rate*self.time elif self.rate is not None and self.distance is not None: self['time'] = self.distance / self.rate elif self.time is not None and self.distance is not None: self['rate'] = self.distance / self.time
The dict
type uses __init__()
to populate the internal dictionary, then tries to solve if enough data is present. It uses __setattr__()
to add new items to the dictionary. It also attempts to solve the equation each time a value is set.
In __getattr__()
, we use None
to indicate a missing value from the equation. This allows us to set an attribute to None
to indicate that it is a missing value, and this will force the solution to look for this value. For example, we might do this based on user inputs or a network request where all parameters were given a value but one variable was set to None
.
We can use it as follows:
>>> rtd= RateTimeDistance( rate=6.3, time=8.25, distance=None ) >>> print( "Rate={rate}, Time={time}, Distance={distance}".format( **rtd ) ) Rate=6.3, Time=8.25, Distance=51.975