- Mastering Objectoriented Python
- Steven F. Lott
- 546字
- 2021-11-12 16:25:14
Using a data descriptor
A data descriptor is somewhat trickier to design because it has such a limited interface. It must have a __get__()
method and it can only have __set__()
or __delete__()
. This is the entire interface: from one to three of these methods and no other methods. Introducing an additional method means that Python will not recognize the class as being a proper data descriptor.
We'll design an overly simplistic unit conversion schema using descriptors that can do appropriate conversions in their __get__()
and __set__()
methods.
The following is a superclass of a descriptor of units that will do conversions to and from a standard unit:
class Unit: conversion= 1.0 def __get__( self, instance, owner ): return instance.kph * self.conversion def __set__( self, instance, value ): instance.kph= value / self.conversion
This class does simple multiplications and divisions to convert standard units to other non-standard units and vice versa.
With this superclass, we can define some conversions from a standard unit. In the previous case, the standard unit is KPH (kilometers per hour).
The following are the two conversion descriptors:
class Knots( Unit ): conversion= 0.5399568
class MPH( Unit ): conversion= 0.62137119
The inherited methods are perfectly useful. The only thing that changes is the conversion factor. These classes can be used to work with values that involve unit conversion. We can work with MPH's or knots interchangeably. The following is a unit descriptor for a standard unit, kilometers per hour:
class KPH( Unit ): def __get__( self, instance, owner ): return instance._kph def __set__( self, instance, value ): instance._kph= value
This class represents a standard, so it doesn't do any conversion. It uses a private variable in the instance to save the standard value for speed in KPH. Avoiding any arithmetic conversion is simply a technique of optimization. Avoiding any reference to one of the public attributes is essential to avoiding infinite recursions.
The following is a class that provides a number of conversions for a given measurement:
class Measurement: kph= KPH() knots= Knots() mph= MPH() def __init__( self, kph=None, mph=None, knots=None ): if kph: self.kph= kph elif mph: self.mph= mph elif knots: self.knots= knots else: raise TypeError def __str__( self ): return "rate: {0.kph} kph = {0.mph} mph = {0.knots} knots".format(self)
Each of the class-level attributes is a descriptor for a different unit. The get and set methods of the various descriptors will do appropriate conversions. We can use this class to convert speeds among a variety of units.
The following is an example of an interaction with the Measurement
class:
>>> m2 = Measurement( knots=5.9 ) >>> str(m2) 'rate: 10.92680006993152 kph = 6.789598762345432 mph = 5.9 knots' >>> m2.kph 10.92680006993152 >>> m2.mph 6.789598762345432
We created an object of the Measurement
class by setting various descriptors. In the first case, we set the knots descriptor.
When we displayed the value as a large string, each of the descriptor's __get__()
methods was used. These methods fetched the internal kph
attribute value from the owning object, applied a conversion factor, and returned the resulting value.
The kph
attribute also uses a descriptor. This descriptor does not do any conversion; however, it simply returns a private value cached in the owning object. The KPH
and Knots
descriptors require that the owning class implement a kph
attribute.