Descriptor class in Python

Source: Internet
Author: User
Here we will explain in detail the Descriptor class in Python in the future, including defining the Descriptor and displaying how to call the Descriptor. if you need a friend, refer to the Descriptor as a class to reconcile attribute access. The descriptor class can be used to obtain, set, or delete attribute values. Descriptor objects are constructed in a class during class definition.

Generally, a descriptor is an object property with binding behavior. the access to its attribute is overwritten by the descriptor protocol method. These methods are _ get _ (), _ set _ (), and _ delete __(), an object is called a descriptor as long as it contains the three methods (Translator's note: contains at least one.
The default behavior of attribute access is to get, set, and delete attributes from the Dictionary of an object. Example:. the search chain of x starts with. _ dict _ ['x'], followed by type (). _ dict _ ['x'], followed by the base class except the meta class of type (a). note: If the inheritance tree is very deep, may access multiple base classes ). If the searched value is an object that contains a descriptor method, Python may overwrite the default behavior of this object and call that descriptor method. Note that the descriptor is called only in the new object or new class (inherited from object or type.
Descriptor is a powerful and common protocol. They are the implementation mechanisms behind properties, methods, static methods, class methods, and super. They are widely used in Python 2.2 to implement new classes. The descriptor simplifies the underlying C code and provides a flexible new tool for Python programming.

The descriptor design pattern has two parts: an owner class and the attribute descriptor itself. The owner class uses one or more descriptors for its attributes. The descriptor class defines a combination of methods for obtaining, setting, and deleting. An instance of the descriptor class will be an attribute of the owner class.

The feature is a method function based on the owner class. Unlike a feature, a descriptor is an instance of a class, which is different from the owner class. Therefore, descriptors are generally reusable general attributes. The owner class can have multiple instances of different descriptor classes to manage attributes with similar behaviors.

Unlike other attributes, descriptors are created at the class level. They are not created during _ init () _ initialization. However, the descriptor value can be set during initialization. the descriptor is usually built outside of any method function as part of the class.

When the owner class is defined, each descriptor object is bound to a descriptor class instance with different class-level attributes.

Is identified as a descriptor, and a class must implement any combination of the following three methods.

  • Descriptor. _ get _ (self, instance, owner)-> object: In this method, the instance parameter is the self variable of the object to be accessed. The owner parameter is the object of the owner class. If this descriptor is called in the context of the class, the instance parameter will get a value of None. This must return the descriptor value.
  • Descriptor. _ set _ (self, instance, value): In this method, the instance parameter is the self variable of the object to be accessed. The value parameter is the new value to be set for the descriptor.
  • Descriptor. _ delete _ (self, instance) in this method, the instance parameter is the self variable of the object to be accessed. The method of this descriptor must delete the value of this attribute.

Sometimes, a descriptor class also needs a _ init _ () method function to initialize the internal state of the descriptor.

There are two descriptors based on defined methods, as shown below:

1. non-data descriptor:This descriptor defines either _ set _ () or _ delete _ () or both. It cannot define _ get __(). Non-data descriptor objects are often used as part of expressions. It may be a Callable object, or it may have its own properties or methods. An unchangeable non-data descriptor must implement _ set _ (), but it may only throw AttributeError. These descriptors are easy to design because the interfaces are more flexible.
2. data descriptor:This descriptor defines at least _ get __(). Generally, it defines _ get _ () and _ set _ () to create a variable object. Since descriptors will be invisible to a large extent, attributes or methods cannot be further defined. The reference of an attribute has a data descriptor that is delegated to the _ get _ (), _ set _ (), or _ delete _ () methods of the descriptor. These are difficult to design, so let's look at them later.
There are various use cases for descriptors. Internally, Python uses descriptors for the following reasons:

  • In the hidden interior, the class method is implemented as a descriptor. These non-data descriptors apply method functions to objects and different parameter values.
  • The property () function creates a data descriptor for a field.
  • A class method or static method is implemented as a descriptor; this is applied to the class to replace the class instance.

When we consider the purpose of a descriptor, we must also test three common use cases for data as the descriptor to work normally, as shown below:

  • The descriptor object has data or obtains data. In this case, the self variable of the descriptor object is meaningful and the descriptor is stateful. The _ get _ () method of the data descriptor returns the internal data. A non-data descriptor has other methods or attributes to access the data.
  • The owner instance that contains data. In this case, the descriptor object must use the instance parameter to reference the value to the owner object. The _ get _ () method of the data descriptor obtains data from the instance. Other methods are available to access instance data for non-data descriptors.
  • The owner class that contains the relevant data. In this case, the descriptor object must use the owner parameter. This is commonly used when descriptors implement static methods or class methods applied to the entire class.

We will take a closer look at the first case. Let's see how to create a data descriptor with The _ get _ () and _ set _ () methods. We will also look at the creation of non-data descriptors without the _ get _ () method.

The second case (data in the owner instance) shows what the @ property modifier has done. A possible advantage is that descriptors have a traditional feature that moves computing from the owner class to the descriptor class. This tends to be the sharding class design and may not be the best method. If the computation is truly epic and complex, the strategy model may be better.

The third case shows how the @ staticmethod and @ classmethod decorator are implemented. We don't need to re-invent the wheel.

1. use non-data descriptors
We often have small objects closely bound with attribute values. For this example, we will look at the actions to bind the value to the unit.

Below is a simple non-data descriptor class, which lacks a _ get _ () method:

class UnitValue_1:  """Measure and Unit combined."""  def __init__(self, unit):    self.value = None    self.unit = unit    self.default_format = "5.2f"  def __set__(self, instance, value):    self.value = value  def __str__(self):    return "{value:{spec}} {unit}"    .format(spec=self.default_format, **self.__dict__)  def __format__(self, spec="5.2f"):    #print( "formatting", spec )    if spec == "":      spec = self.default_format    return "{value:{spec}} {unit}".format(spec=spec, **self.__dict__)

This class defines a pair of simple values, one variable (value) and the other valid immutable object (unit ).

When this descriptor is accessed, the descriptor object itself is available and other methods or attributes of the descriptor can be used. We can use this descriptor to create a class to manage dimensions and other values related to physical units.

The following is a class for early calculation of speed-time-distance:

class RTD_1:  rate = UnitValue_1("kt")  time = UnitValue_1("hr")  distance = UnitValue_1("nm")  def __init__(self, rate=None, time=None, distance=None):    if rate is None:      self.time = time      self.distance = distance      self.rate = distance / time    if time is None:      self.rate = rate      self.distance = distance      self.time = distance / rate    if distance is None:      self.rate = rate      self.time = time      self.distance = rate * time  def __str__(self):    return "rate: {0.rate} time: {0.time} distance:{0.distance}".format(self)

Once an object is created and its attributes are loaded, the lost value is calculated. Once calculated, the descriptor can check the names of the obtained values or units. In addition, the descriptor has a convenient response and request format for str.

The interaction between the descriptor and the RTD_1 class is as follows:

>>> m1 = RTD_1(rate=5.8, distance=12)>>> str(m1)'rate: 5.80 kt time: 2.07 hr distance: 12.00 nm'>>> print("Time:", m1.time.value, m1.time.unit)Time: 2.0689655172413794 hr

We have created an RTD_1 instance with the rate and distance parameters. These are the _ set _ () methods used to calculate the rate and distance descriptors.

When we request str (m1), this will calculate all str () methods of RTD_1, and instead use the _ format _ () method of the rate, time, and distance descriptor. This provides us with numbers and units.

Since non-data descriptors do not have _ get _ () and do not return their internal values, we can access a single element of the descriptor.

2. use the data descriptor
The data descriptor design is more complex because it has restrictions on interfaces. It must have a _ get _ () method and can only have _ set _ () or _ delete __(). This is all interfaces: there are no other methods for these methods from one to three. Introducing an additional method means that Python does not treat this class as a correct data descriptor.

We will use descriptors to design a simple unit conversion mode. we can perform proper conversions in the _ get _ () and _ set _ () methods.

The following is a hyperclass of the unit descriptor, which converts between other units and standard units:

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 converts standard units to other non-standard units by simple multiplication and division, and vice versa.

With this superclass, we can define some conversions from a standard unit. In the previous example, the standard unit is kilometers (km/hour ).

The two conversion descriptors are as follows:

class Knots(Unit):  conversion = 0.5399568class MPH(Unit):  conversion = 0.62137119

The inheritance method is very useful. The only change is the conversion factor. These classes can be used to process values that involve unit conversions. We can process miles per hour or interchangeable node. The following is a standard unit descriptor, 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 no conversion is made. It uses a private variable instance to save the standard value of a kilometer per hour. Avoiding any arithmetic conversions is a simple technical optimization. It is vital to avoid any reference to a public field and avoid infinite recursion.

The following class provides a group of transformations for a given size:

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 class-level attribute of different units is a descriptor. The methods for obtaining and setting various descriptors are converted as appropriate. We can use this class for speed conversion between various units.

The following is an example of interaction with the Measurement class:

>>> m2 = Measurement(knots=5.9)>>> str(m2)'rate: 10.92680006993152 kph = 6.789598762345432 mph = 5.9 knots'>>> m2.kph10.92680006993152>>> m2.mph6.789598762345432

We create a Measurement class object by setting different descriptors. In the first example, we set the node descriptor.

When the displayed value is a large string, the _ get _ () of each descriptor will be used. These methods obtain the internal kph field value from the owner object, apply a conversion factor, and return a result value.

The kph field also uses a descriptor. This descriptor is not converted; however, it only returns the private value cached in the owner object. KPH and Knots descriptors require the owner class to implement a kph attribute.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.