Python descriptor, pythondescriptor

Source: Internet
Author: User

Python descriptor, pythondescriptor

Python 2.2 introduces the descriptor (descriptor) function, which is based on the new-styel class Object Model, it also solves the MRO (Method Resolution Order) Problem in the previous versions of classic class systems, and introduces some new concepts, such as classmethod, staticmethod, super, Property, etc. Therefore, understanding descriptor helps you better understand the running mechanism of Python.

So what is descriptor?

In short, descriptor is a class of objects that implement the _ get _ (), _ set _ (), _ delete _ () methods.

Orz... if you have an epiphany, please accept my knee;
O_o !... Congratulations! This shows that you have great potential. We can continue to explore the potential:

Introduction

For unfamiliar things, a specific Chestnut is the best way to learn. First, let's look at the question: Let's create a class for a math test, used to record the student's student ID and score, and to provide a check function for determining whether the student passes the exam:

class MathScore():    def __init__(self, std_id, score):    self.std_id = std_id    self.score = score  def check(self):    if self.score >= 60:      return 'pass'    else:      return 'failed'      

A simple example looks good:

xiaoming = MathScore(10, 90)xiaoming.scoreOut[3]: 90xiaoming.std_idOut[4]: 10xiaoming.check()Out[5]: 'pass'

But there will be a problem. For example, if a hand shake and a negative score is entered, then he will have to suffer:

xiaoming = MathScore(10, -90)xiaoming.scoreOut[8]: -90xiaoming.check()Out[9]: 'failed'

This is obviously a serious problem. How can we make a 90 + math child fail to take care of the subject? As a result, a simple and crude method was born:

class MathScore():    def __init__(self, std_id, score):    self.std_id = std_id    if score < 0:      raise ValueError("Score can't be negative number!")    self.score = score  def check(self):    if self.score >= 60:      return 'pass'    else:      return 'failed'          

 
The negative value judgment is added to the above class initialization function. Although it is not elegant enough or even a little poor, it does work well during instance initialization:

xiaoming = MathScore(10, -90)Traceback (most recent call last): File "<ipython-input-12-6faad631790d>", line 1, in <module>  xiaoming = MathScore(10, -90) File "C:/Users/xu_zh/.spyder2-py3/temp.py", line 14, in __init__  raise ValueError("Score can't be negative number!")ValueError: Score can't be negative number!

OK, but we still cannot prevent the instance from assigning values to scores. After all, it is common to modify scores:

Xiaoming = MathScore (10, 90) xiaoming =-10 # An error cannot be identified

For most children's shoes, this problem is so easy: Turn the score into private, thus prohibiting direct calls like xiaoming. score, and adding a get_score and set_score for reading and writing:

class MathScore():    def __init__(self, std_id, score):    self.std_id = std_id    if score < 0:      raise ValueError("Score can't be negative number!")    self.__score = score  def check(self):    if self.__score >= 60:      return 'pass'    else:      return 'failed'            def get_score(self):    return self.__score    def set_score(self, value):    if value < 0:      raise ValueError("Score can't be negative number!")    self.__score = value

This is indeed a common solution, but it is ugly:

You can no longer use xiaoming. score as a natural way to call scores. You need to use xiaoming. get_score (), which looks like stuttering!
There are also underscores and parentheses against humanity... it should only appear in private conversations between computers...
Xiaoming. score = 80 cannot be used for assignment, but xiaoming. set_score (80) must be used. This is too difficult for a math !!!

As a simple and elegant programming language, Python does not care about it, so it provides the Property class:

Property class

No matter what the Property is, let's take a look at how it solves the above problem in a concise and elegant manner:

class MathScore():    def __init__(self, std_id, score):    self.std_id = std_id    if score < 0:      raise ValueError("Score can't be negative number!")    self.__score = score  def check(self):    if self.__score >= 60:      return 'pass'    else:      return 'failed'            def __get_score__(self):    return self.__score    def __set_score__(self, value):    if value < 0:      raise ValueError("Score can't be negative number!")    self.__score = value      score = property(__get_score__, __set_score__)

Compared with the previous code, a property instance is instantiated in the last sentence and named score. At this time, we can naturally read and write instance. _ score:

xiaoming = MathScore(10, 90)xiaoming.scoreOut[30]: 90xiaoming.score = 80xiaoming.scoreOut[32]: 80xiaoming.score = -90Traceback (most recent call last): File "<ipython-input-33-aed7397ed552>", line 1, in <module>  xiaoming.score = -90 File "C:/Users/xu_zh/.spyder2-py3/temp.py", line 28, in __set_score__  raise ValueError("Score can't be negative number!")ValueError: Score can't be negative number!

WOW ~~ Everything works normally!
Well, the question is: how does it work?
Let's take a look at the property parameters first:

Class property (fget = None, fset = None, fdel = None, doc = None) # copy from Python official documentation
How it works:

Instantiate a property instance (I know this is nonsense );
If you call a property instance (such as xiaoming. score), fget is called directly and the corresponding value is returned by fget;
If you assign a value to a property instance (xiaoming. score = 80), the fset is called and the fset defines the corresponding operation;
If you delete a property instance (del xiaoming), you can call fdel to delete the instance;
Doc is the character description of the property instance;
Fget/fset/fdel/doc must be customized. If only fget is set, the instance is a read-only object;
This seems like the features of the descriptor mentioned at the beginning of this article. Let's review the descriptor:

"Descriptor is a class of objects that implement the _ get _ (), _ set _ (), _ delete _ () methods ."

@~ @ If you understand this time, please accept my knee again Orz...

In addition, Property also has a modifier syntax sugar @ property, which implements the same functions as property:

Class MathScore (): def _ init _ (self, std_id, score): self. std_id = std_id if score <0: raise ValueError ("Score can't be negative number! ") Self. _ score = score def check (self): if self. _ score> = 60: return 'pass' else: return 'failed' @ property def score (self): return self. _ score @ score. setter def score (self, value): # note that the method name must be consistent with the preceding one. Otherwise, if value <0: raise ValueError ("Score can't be negative number! ") Self. _ score = value

Now that we know how the property instance works, the question is: How is it implemented?
In fact, Property is indeed implemented based on descriptor. Let's go to our topic descriptor!

Descriptor

In this case, no matter what the descriptor is, let's take a look at the chestnuts first. For the functions implemented by the Property above, we can implement them through the custom descriptor:

Class NonNegative (): def _ init _ (self): pass def _ get _ (self, ist, cls): return 'descriptor get: '+ str (ist. _ score) # Add a character description here to make it easy to see the call def _ set _ (self, ist, value): if value <0: raise ValueError ("Score can't be negative number! ") Print ('scriptor set: ', value) ist. _ score = value class MathScore (): score = NonNegative () def _ init _ (self, std_id, score): self. std_id = std_id if score <0: raise ValueError ("Score can't be negative number! ") Self. _ score = score def check (self): if self. _ score> = 60: return 'pass' else: return 'failed'

We have defined a new NonNegative class, implemented the _ get _ and _ set _ methods in it, and then instantiated a NonNegative instance score in the MathScore class, note !!! Important: The score instance is a class attribute of MathScore !!! Class Attribute !!! Class Attribute !!! This Mathscore. the score attribute has the same function as the score instance of the same Property, except for Mathscore. get and set called by score are not defined in Mathscore, but in NonNegative class. NonNegative class is a descriptor object!

Nana? In the definition of NonNegative class, there can be no half "descriptor". How can this become a descriptor object ???

Calm down! The important thing is to say it only once: the descriptor object is a class at most in any implementation of the _ get __,__ set _ or _ delete _ method. Therefore, NonNegative is naturally a descriptor object.

So what is the difference between a descriptor object and a common analogy? First, let's take a look at the effects of the Upper-end code:

xiaoming = MathScore(10, 90)xiaoming.scoreOut[67]: 'descriptor get: 90'xiaoming.score = 80descriptor set: 80wangerma = MathScore(11, 70)wangerma.scoreOut[70]: 'descriptor get: 70'wangerma.score = 60Out[70]: descriptor set: 60wangerma.scoreOut[73]: 'descriptor get: 60'xiaoming.scoreOut[74]: 'descriptor get: 80'xiaoming.score = -90ValueError: Score can't be negative number!

MathScore. although score is a class attribute, it can be assigned values through instances, and there is no conflict between the assignment and call of different MathScore instances xiaoming and wangerma! Therefore, it seems to be more similar to the instance attribute of MathScore, but unlike the instance attribute, it does not use the read/write method operation value of the MathScore instance, but how does it always perform operations through the values of the NonNegative instance's _ get _ and _ set?

Take note of the _ get _ and _ set _ parameters.

Def _ get _ (self, ist, cls): # self: descriptor instance itself (such as Math. score), ist: the class that calls the score instance (such as xiaoming), cls: descriptor instance (such as MathScore)
...

Def _ set _ (self, ist, value): # score is used to call and rewrite MathScore and its specific instance attributes through the passed ist and cls parameters.
...
Okay. Now we have figured out how the descriptor instance simulates the instance attributes of the host class. In fact, the implementation of the Property instance is similar to that of the NonNegative instance above. Now that we have Propery, why do we need to customize descriptor?

The answer is: more realistic simulation of instance attributes (think about the disgusting judgment statement in MathScore. _ init _), and the most important thing is: code reuse !!!

In short: A single descriptor object can simulate instance attributes more realistically and operate on multiple instance attributes of the host class instance.

O. O! If you understand it again, you can jump to the following to write a comment...

Let's take a look: if we need to judge whether a student's score is negative and whether the student's student ID is negative, the implementation of property is like this:

class MathScore():    def __init__(self, std_id, score):    if std_id < 0:      raise ValueError("Can't be negative number!")    self.__std_id = std_id    if score < 0:      raise ValueError("Can't be negative number!")    self.__score = score  def check(self):    if self.__score >= 60:      return 'pass'    else:      return 'failed'          @property    def score(self):    return self.__score    @score.setter  def score(self, value):    if value < 0:      raise ValueError("Can't be negative number!")    self.__score = value    @property  def std_id(self):    return self.__std_id  @std_id.setter  def std_id(self, idnum):    if idnum < 0:      raise ValueError("Can't be negative nmuber!")    self.__std_id = idnum

The biggest problem with a Property instance is:

The initialization of the host class instance cannot be affected, so we must add the ugly if...
A single Property instance can only target a single attribute of a host class instance. If you need to control multiple properties, you must define multiple Property instances. This is really a headache!
However, the custom descriptor can solve this problem well. See the implementation below:

Class NonNegative (): def _ init _ (self): self. dic = dict () def _ get _ (self, ist, cls): print ('description get', ist) return self. dic [ist] def _ set _ (self, ist, value): print ('description set', ist, value) if value <0: raise ValueError ("Can't be negative number! ") Self. dic [ist] = value class MathScore (): score = NonNegative () std_id = NonNegative () def _ init _ (self, std_id, score ): # MathScore is called instead of the Instance attributes std_id and score. std_id and MathScore. score self. std_id = std_id self. score = score def check (self): if self. score> = 60: return 'pass' else: return 'failed'

Haha ~! MathScore. _ init _ finally has no if, and the code is much simpler than above, but there are many functions, and the instances do not affect each other:

In fact, the same attribute of multiple MathScore instances is operated by the corresponding class attribute of a single MathScore class (that is, a NonNegative instance), which is consistent with the property, but how does it overcome the two shortcomings of Property? There are three secrets:

In essence, a Property instance uses class attributes to perform operations on instance properties. NonNegative instances use class attributes to simulate instance properties. Therefore, the instance properties do not exist;

NonNegative instances use dictionaries to record each MathScore instance and its corresponding attribute values. The key is the name of the MathScore instance. For example, the score instance uses dic = {'zhangsan': 50, 'lisi ': 90} records the score value corresponding to each instance to ensure that the MathScore instance attributes can be simulated;
MathScore directly calls class attributes within _ init _ to simulate the initialization and assignment of instance attributes, which is impossible because of the Property instance (that is, the class attribute of MathScore) it is a real operation of the Instance attributes passed in by the MathScore instance to achieve the purpose, but if it is not an instance attribute passed in the initialization program, but a class attribute (that is, the Property instance itself ), it will fall into infinite recursion (PS: If you want to implement self in the previous property instance. _ score is changed to self here. what will happen to score ).

The three points seem to be understandable. It doesn't matter. Here is a metaphor:

Each descriptor instance (MathScore. score and MathScore. std_id) is a basket in the class scope. The basket contains a box with the name of each MathScore instance ('zhangsan', 'lisi '), the box in the same basket only records the value of the same attribute (for example, the box in the score basket only records the value of the score). When the MathScore instance operates on the corresponding attribute, find the corresponding basket, retrieve the box with the instance name, and operate on it.

Therefore, the attributes of an instance are not in the instance's own scope, but in the class scope basket, but we can use xiaoming. the actual calling logic of score is as follows: the instance on the right performs operations on score and std_id through the red line and black line, respectively, they first call the corresponding class attributes through the class, then the class attributes process the operation through the corresponding descriptor instance scope, and return the corresponding results to the class attributes, and finally let the instance perceive.

As you can see, many shoes may not be calm, because we all know that xiaoming is used in Python. score = 10. If xiaoming does not have an instance attribute such as score, the instance attribute will be automatically created. How can we call the score of MathScore?

First, applaud !!! I would like to give my shoes a thumbs up !!! In fact, this problem arises when we talk about Property.

Second, in order to implement the discriptor, Python makes corresponding adjustments to the calling sequence of the attributes, which will be described in "Python descriptor (below.

Articles you may be interested in:
  • An infinite recursion problem caused by python's descriptor and property
  • Detailed description of using Descriptor to implement class-Level Properties in Python
  • Descriptor
  • Describes the usage of the @ property modifier in Python
  • Descriptor)
  • Python Property attribute usage

Related Article

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.