Python introduced the descriptor (descriptor) feature in version 2.2, and it is this feature that implements the object model of the new Class (New-styel Class) and addresses the multiple inheritance in the classic class system in previous versions. MRO (Method Resolution Order) problem, but also introduced a number of new concepts, such as Classmethod, Staticmethod, Super, property and so on. Understanding descriptor therefore helps to better understand Python's operational mechanisms.
So what is descriptor?
In short: Descriptor is a class of objects that implement the __get__ (), __set__ (), __delete__ () methods.
Orz ... If you have an instant epiphany, take my knees.
O_o!... If indefinitely, then congratulations! It means you have great potential and we can keep digging:
Introduction
For unfamiliar things, a specific chestnut is the best way to learn, first of all to look at the question: Suppose we give a math exam a class to record each student's number, math scores, and provide a check function to determine whether to pass the exam:
Class Mathscore (): def __init__ (self, std_id, score): self.std_id = std_id Self.score = score def check (self): if Self.score >=: return ' pass ' else: return ' failed '
A very simple example that looks good to run:
Xiaoming = Mathscore (Ten, a) xiaoming.scoreout[3]: 90xiaoming.std_idout[4]: 10xiaoming.check () out[5]: ' Pass '
But there will be a problem, such as a shaking of the hand to enter a negative score, then he will have to hang the tragedy:
Xiaoming = Mathscore ( -90) xiaoming.scoreout[8]: -90xiaoming.check () out[9]: ' Failed '
This is obviously a serious problem, how to let a math 90+ child, then a simple rough 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 >=: return ' pass ' else: return ' failed '
The addition of a negative judgment in the initialization function of the above class, though not elegant or even a bit clumsy, does work well when the instance is initialized:
Xiaoming = Mathscore ( -90) Traceback (most recent call last): File "
", line 1, in
xiaoming = Mathscore ( -90) File "c:/users/xu_zh/.spyder2-py3/temp.py", line +, in __init__ raise ValueError ("score can ' t be Negative number! ") Valueerror:score can ' t be negative number!
OK, but we still can't stop the assignment of the instance to the score, after all, the changes are often the same thing:
Xiaoming = Mathscore (max.) xiaoming = -10 # Unable to determine error
For most children's shoes, this is a very easy problem: turning score into private, thereby prohibiting direct calls such as 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 >=: 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 a common solution, but it has to be said to be ugly:
Call scores can no longer use xiaoming.score in such a natural way, need to use Xiaoming.get_score (), which looks like stuttering in the talking!
And that anti-human underline and parentheses ... That should only appear in whispers between computers ...
The assignment is also not possible with Xiaoming.score = 80, but with Xiaoming.set_score (80), which is too TM for the math teacher!!!
As a simple and elegant programming language, Python does not sit idly by, so it gives the property class:
Property class
Regardless of what the property is, let's look at how it is simple and elegant to solve the above problem:
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 >=: 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, mainly in the last sentence instantiation of a property instance, and named score, this time, we can so naturally read and write to the Instance.__score:
Xiaoming = Mathscore (Ten, a) xiaoming.scoreout[30]: 90xiaoming.score = 80xiaoming.scoreout[32]: 80xiaoming.score =- 90Traceback (most recent): File "
", line 1, in
xiaoming.score = -90 file "C:/users/xu _zh/.spyder2-py3/temp.py ", line __set_score__, in the raise ValueError (" score can ' t be negative number! ") Valueerror:score can ' t be negative number!
Wow~~ Everything works fine!
Well, here's the question: How does it work?
First look at the property parameters:
Class Property (Fget=none, Fset=none, Fdel=none, Doc=none) #拷贝自 official Python Document
The way it works:
Instantiate a property instance (I know this is nonsense);
Invoking a property instance (such as Xiaoming.score) calls Fget directly and returns the corresponding value by Fget;
An assignment to a property instance (Xiaoming.score = 80) invokes Fset and the corresponding action is done by the fset definition;
Deleting a Property instance (Del Xiaoming) will call Fdel to implement the deletion of the instance;
Doc is the character description of the property instance;
Fget/fset/fdel/doc need to be customized, if only fget is set, then the instance is read-only object;
This looks very similar to the descriptor function described at the beginning of this article, so let's review descriptor:
"Descriptor is a class of objects that implement the __get__ (), __set__ (), __delete__ () methods. ”
@~@ If you understand this time, please accept my knees again Orz ...
In addition, the property also has an adorner syntax sugar @property, which implements exactly the same function as the 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 >=: return ' pass ' else: return ' Failed ' @property def score (self): return Self.__score @score. Setter def score (self, Value): #注意方法名称要与上面一致, otherwise invalid 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 comes again: How does it work?
In fact, the property is really based on descriptor and realized, the following into our topic descriptor it!
Descriptor descriptor
So first, regardless of descriptor is what, we still look at chestnuts first, for the above property realization function, we can through the custom descriptor to realize:
Class nonnegative (): def __init__ (self): pass def __get__ (Self, ist, CLS): return ' descriptor get: ' + STR (ist.__score) #这里加上字符描述便于看清调用 def __set__ (self, IST, value): if value < 0: raise ValueError ("Score Can ' t be negative number! ") Print (' Descriptor 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 Negati ve number! ") Self.__score = score def check (self): if Self.__score >=: return ' pass ' else: return ' Failed
We define a new nonnegative class and implement the __get__, __set__ method in it, then instantiate a nonnegative instance in the Mathscore class score, note!!! Important thing to say three times: score instance is the class attribute of Mathscore!!! Class Properties!!! Class Properties!!! This mathscore.score attribute is the same as the function of the score instance of the property above, except that the Mathscore.score call's get, set is not defined within Mathscore, but is defined in the Nonnegative class , and the Nonnegative class is a descriptor Object!
Nani? The definition of nonnegative class can not see half "descriptor" the word, how to become the Descriptor object???
Calm! The important thing to say here only once: any class that implements one or more of the __get__,__set__ or __DELETE__ methods is the Descriptor object. So nonnegative is naturally a descriptor object.
So what's so special about descriptor objects and common analogies? First of all, take a look at the effect of the upper code:
Xiaoming = Mathscore (Ten) xiaoming.scoreout[67]: ' descriptor get:90 ' Xiaoming.score = 80descriptor Set:80wangerma = Mat Hscore (one, 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!
It can be found that although the Mathscore.score is a class property, it can be assigned by the instance, and face different Mathscore instance xiaoming, Wangerma assignment and invocation, will not create a conflict! So it looks more like an instance property of Mathscore, but unlike an instance property, it does not manipulate the value through the read-write method of the Mathscore instance, but always operates on the value through the __get__ and __set__ of the nonnegative instance. So how does it do that?
Pay attention to the parameters of __get__ and __set__
def __get__ (Self, ist, CLS): #self: descriptor instance itself (e.g. Math.score), ist: Invoking an instance of score (such as Xiaoming), Cls:descriptor The class where the instance resides (for example, Mathscore)
...
def __set__ (self, IST, value): #score is to implement calls and overrides to Mathscore and its specific instance properties through these incoming ist, CLS parameters
...
OK, now we have a basic understanding of how the descriptor instance is implemented to simulate the instance properties of the host class. In fact, the property instance is implemented in a similar way to the nonnegative instance above. So why do we have to customize descriptor now that we have propery?
The answer is: more realistic simulation of instance properties (think of mathscore.__init__ inside that disgusting judgment statement), and most importantly: Code REUSE!!!
In short: With a single Descriptor object, you can simulate instance properties more realistically, and you can implement multiple instance properties of a host class instance.
O.o! If you understand the second, then you can jump directly to the following to write a review ...
See a chestnut: if not only to determine whether the student's score is negative, but also to judge whether the student's number is negative, the use of property is the way to achieve:
Class Mathscore (): def __init__ (self, std_id, score): if std_id < 0: raise ValueError ("Can ' t is negative n umber! ") self.__std_id = std_id If score < 0: raise ValueError ("Can ' t be negative number!") Self.__score = score def check (self): if Self.__score >=: 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 property instances is:
Cannot affect the initialization of the host class instance, so we must add the ugly if ... in the __init__.
A single property instance can only be targeted to a single attribute of the host class instance, and if multiple properties need to be controlled, you must define multiple property instances, which is really painful!
But custom descriptor can solve this problem well and see the implementation:
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): #这里并未创建实例属性 std_id and score, but calls mathscore.std_id and mathscore.score self.std_id = std_id Self.score = score def check (self): if Self.score >=: return ' pass ' else: return ' Failed '
Haha! Mathscore.__init__ is finally gone. If, the code is more concise than the above, but a lot of functions, and the instances do not affect each other:
In fact, Mathscore multiple instances of the same property, all through the corresponding class properties of a single Mathscore class (also known as the nonnegative instance) operation, which is consistent with the property, but it is how to overcome the two shortcomings? There are three tips:
Property instances essentially use class properties to manipulate instance properties, while nonnegative instances simulate instance properties entirely through class attributes, so instance properties do not exist at all;
The nonnegative instance uses a dictionary to record each Mathscore instance and its corresponding property value, where key is the Mathscore instance name: For example, score instance is using DIC = {' Zhangsan ': 90} Records each instance The corresponding score value, thus ensuring that the simulation of Mathscore instance properties can be achieved;
Mathscore simulates the assignment of an instance property initialization by invoking the class property directly within the __init__, while the properties are not possible because the property instance (also known as the Mathscore class attribute) is the real operation Mathscore Instance into an instance property for the purpose, but if the class attribute (or the property instance itself) is passed in to the initialization program, it will fall into infinite recursion (PS: Think about it if you change the Self.__score in the previous property instance implementation to the What will happen to Self.score).
This three-point look at the indefinitely, it doesn't matter, to a metaphor:
Each descriptor instance (Mathscore.score and mathscore.std_id) is a basket in the class scope with a box with each Mathscore instance name in the basket (' Zhangsan ', ' Lisi '), The box in the same basket only records the value of the same property (for example, the box in the score basket records only the fractional value), and when the Mathscore instance operates on the corresponding attribute, it finds the corresponding basket, takes out the box labeled with the instance name, and operates on it.
Therefore, the property of the instance is not in the scope of the instance itself at all, but in the basket of the class scope, but we can do it by xiaoming.score this way. So the logic of its actual invocation is this: the instances on the right operate on score and std_id, respectively, through the red and black lines, and they first invoke the corresponding class property through the class, and then the class property processes the operation through the corresponding descriptor instance scope and returns the corresponding result to the Class property. Finally let the instance perceive.
See here, many children's shoes may not be calm, because everyone knows in Python to take Xiaoming.score = 10 Such an assignment, if Xiaoming does not score such an instance property, it will automatically create the instance property, how to call Mathscor What about the score of E?
First of all, to applaud!!! To think of this point of child shoes praise!!! In fact, this problem arises when the property is mentioned above.
Secondly, in order to implement Discriptor, Python does make corresponding adjustments to the order of the attributes, which will be described in "Python descriptor (bottom)".