This article introduces Descriptor, a key knowledge point in Python. descriptor is the longest feature that troubles me in the core of Python, however, once you understand it, the descriptor does have its application value. I haven't written the Flask code for a long time. it's really embarrassing to think about it. I don't want to write the Flask code this time. I don't want you to beat me. (it's just so cheap. you have the ability to bite me.
This time I will write a very important thing about Python, that is, Descriptor (Descriptor)
First-recognized descriptor
Old rules: Talk is cheap, Show me the code. let's take a look at a piece of code.
classPerson(object):""""""#----------------------------------------------------------------------def__init__(self, first_name, last_name):"""Constructor""" self.first_name = first_name self.last_name = last_name#---------------------------------------------------------------------- @propertydeffull_name(self):""" Return the full name """return"%s %s"% (self.first_name, self.last_name)if__name__=="__main__": person = Person("Mike","Driscoll") print(person.full_name)# 'Mike Driscoll' print(person.first_name)# 'Mike'
Everyone in this generation must be familiar with it. well, no one knows about property. But do you know the implementation mechanism of property? What is unclear? I still want to learn Mao's Python... Joke. let's take a look at the following code.
classProperty(object):"Emulate PyProperty_Type() in Objects/descrobject.c"def__init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdelifdocisNoneandfgetisnotNone: doc = fget.__doc__ self.__doc__ = docdef__get__(self, obj, objtype=None):ifobjisNone:returnselfifself.fgetisNone:raiseAttributeError("unreadable attribute")returnself.fget(obj)def__set__(self, obj, value):ifself.fsetisNone:raiseAttributeError("can't set attribute") self.fset(obj, value)def__delete__(self, obj):ifself.fdelisNone:raiseAttributeError("can't delete attribute") self.fdel(obj)defgetter(self, fget):returntype(self)(fget, self.fset, self.fdel, self.__doc__)defsetter(self, fset):returntype(self)(self.fget, fset, self.fdel, self.__doc__)defdeleter(self, fdel):returntype(self)(self.fget, self.fset, fdel, self.__doc__)
It looks complicated. let's take a step-by-step look. However, we first come to the conclusion that Descriptors is a special object that implements _ get _, _ set __, the three special methods: _ delete.
Detailed description descriptor
About Property
In the above section, we provide the Propery implementation code. now let's talk about this in detail.
classPerson(object):""""""#----------------------------------------------------------------------def__init__(self, first_name, last_name):"""Constructor""" self.first_name = first_name self.last_name = last_name#---------------------------------------------------------------------- @Propertydeffull_name(self):""" Return the full name """return"%s %s"% (self.first_name, self.last_name)if__name__=="__main__": person = Person("Mike","Driscoll") print(person.full_name)# 'Mike Driscoll' print(person.first_name)# 'Mike'
First, if you do not know about the decorator, you may want to read this article. In short, before we officially run the code, our interpreter will scan our code and replace the part involving the decorator. Similar to the class decorator. In the preceding code
@Propertydeffull_name(self):""" Return the full name """return"%s %s"% (self.first_name, self.last_name)
This process is triggered, that is, full_name = Property (full_name ). Then, after the object is instantiated, we call person. full_name is equivalent to person. full_name. _ get _ (person) then triggers the return self written in the _ get _ () method. fget (obj) is the execution code in def full_name originally compiled by us.
At this time, the comrades can think about the specific running mechanism of getter (), setter (), and deleter () =. = If you still have any questions, you are welcome to discuss them in the comments.
Descriptor
Remember the definition we mentioned earlier: Descriptors is a special object that implements _ get _, _ set __, the three special methods: _ delete. In the instructions in the Python official documentation, to reflect the importance of descriptors, there is a saying: "They are the mechanisms behind properties, methods, static methods, class methods, and super (). they are used throughout Python itself to implement the new style classes introduced in version 2.2. in short, there is a descriptor first, and then one day, second, second air. In the new class, attributes, method calls, static methods, and class methods are all specific descriptor-based usage.
OK. You may want to ask, why is descriptor so important? Don't worry. let's take a look.
Use descriptor
First, read the next code section.
ClassA (object): # Note: In Python 3.x, you do not need to explicitly specify to inherit from the object class when using the new class. if you use Python 2.X( x> 2) is required
defa(self):passif__name__=="__main__": a=A() a.a()
Everyone notices that we have such a statement A.A (). Okay, now let's think about what happened when we called this method?
OK? Come up? No? Okay. let's continue.
First, when we call an attribute, whether it is a member or a method, we will trigger such a method to call the attribute _ getattribute _ (), in our _ getattribute __() if the attribute we try to call implements our descriptor protocol, such a call process type (a) will be generated ). _ dict _ ['A']. _ get _ (B, type (B )). Okay, here we have to draw a conclusion: "In such a call process, there is such a priority order. if the property we are trying to call is a data descriptors, whether or not this attribute exists in the _ dict _ Dictionary of our instance, the _ get _ method in our descriptor is preferentially called, if the property we are trying to call is a non data descriptors, we will first call the existing property in _ dict _ in our instance. if the property does not exist, search for the attributes contained in _ dict _ in the parent class based on the corresponding principles. if the attribute exists, call the _ get _ method, if not, call the _ getattr _ () method ". Is it abstract? Let's talk about it right away. but here, we need to explain data descriptors and non data descriptors first. let's look at an example. What are data descriptors and non data descriptors? In fact, it is very simple. in the descriptor, the descriptor of the _ get _ and _ set _ protocols is data descriptors, if only the _ get _ protocol is implemented, it is non data descriptors. Now let's look at an example:
importmathclasslazyproperty:def__init__(self, func): self.func = funcdef__get__(self, instance, owner):ifinstanceisNone:returnselfelse: value = self.func(instance) setattr(instance, self.func.__name__, value)returnvalueclassCircle:def__init__(self, radius): self.radius = radiuspass @lazypropertydefarea(self): print("Com")returnmath.pi * self.radius *2deftest(self):passif__name__=='__main__': c=Circle(4) print(c.area)
Okay. let's take a closer look at this code. First, we will replace the class descriptor @ lazyproperty. as we have mentioned above, we will not repeat it. Next, we call c. when using area, we first query whether there is an area descriptor in _ dict _ of instance c, and then find that there is neither a descriptor nor such an attribute in c, then, we query _ dict _ in the Circle, and find the attribute named "area", which is also a non-data descriptors. because the area attribute does not exist in our instance dictionary, then, we will call the _ get _ method of area in the dictionary class, and register the attribute area for the instance Dictionary by calling the setattr method in the _ get _ method. Next, we call c. when using area, we can find the existence of the area attribute in the instance dictionary, and the area in the class dictionary is a non data descriptors, therefore, the _ get _ method implemented in the code is not triggered, but the attribute value is directly obtained from the instance Dictionary.
Descriptor usage
Descriptor is widely used, but its main purpose is to make the call process controllable. Therefore, when we need to implement fine-grained control over our call process, we use descriptors, such as the example we mentioned earlier.
classlazyproperty:def__init__(self, func): self.func = funcdef__get__(self, instance, owner):ifinstanceisNone:returnselfelse: value = self.func(instance) setattr(instance, self.func.__name__, value)returnvaluedef__set__(self, instance, value=0):passimportmathclassCircle:def__init__(self, radius): self.radius = radiuspass @lazypropertydefarea(self, value=0): print("Com")ifvalue ==0andself.radius ==0:raiseTypeError("Something went wring")returnmath.pi * value *2ifvalue !=0elsemath.pi * self.radius *2deftest(self):pass
The descriptor feature is used to implement lazy loading. for example, we can control the attribute value assignment.
classProperty(object):"Emulate PyProperty_Type() in Objects/descrobject.c"def__init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdelifdocisNoneandfgetisnotNone: doc = fget.__doc__ self.__doc__ = docdef__get__(self, obj, objtype=None):ifobjisNone:returnselfifself.fgetisNone:raiseAttributeError("unreadable attribute")returnself.fget(obj)def__set__(self, obj, value=None):ifvalueisNone:raiseTypeError("You can`t to set value as None")ifself.fsetisNone:raiseAttributeError("can't set attribute") self.fset(obj, value)def__delete__(self, obj):ifself.fdelisNone:raiseAttributeError("can't delete attribute") self.fdel(obj)defgetter(self, fget):returntype(self)(fget, self.fset, self.fdel, self.__doc__)defsetter(self, fset):returntype(self)(self.fget, fset, self.fdel, self.__doc__)defdeleter(self, fdel):returntype(self)(self.fget, self.fset, fdel, self.__doc__)classtest():def__init__(self, value): self.value = value @PropertydefValue(self):returnself.value @Value.setterdeftest(self, x): self.value = x
As described in the preceding example, we can determine whether the input value is valid.