Home > Backend Development > Python Tutorial > Getting Started with Python Descriptors

Getting Started with Python Descriptors

WBOY
Release: 2016-12-05 13:27:23
Original
1046 people have browsed it

I haven’t written about Flask code for a long time. I feel really ashamed when I think about it. However, I still won’t write about Flask this time. If you don’t accept it, come and hit me (I’m such a bitch, bite me if you can)

This time I will write about a very important thing in Python, namely Descriptor

First introduction to descriptors

Old rule, Talk is cheap, Show me the code. Let’s take a look at a piece of code first

classPerson(object):
""""""

#----------------------------------------------------------------------
def__init__(self, first_name, last_name):
"""Constructor"""
 self.first_name = first_name
 self.last_name = last_name

#----------------------------------------------------------------------
 @property
deffull_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'

Copy after login

Everyone must be familiar with this generation, well, property, who doesn’t know it, but do you know the implementation mechanism of property? What's not clear? How about learning Python? . . Just kidding, let’s look at the following piece of 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 = fdel
ifdocisNoneandfgetisnotNone:
 doc = fget.__doc__
 self.__doc__ = doc

def__get__(self, obj, objtype=None):
ifobjisNone:
returnself
ifself.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__)

Copy after login

Doesn’t it seem complicated? It’s okay. Let’s take a look at it step by step. But here we first give a conclusion: Descriptors are a special kind of object, which implements three special methods: __get__, __set__, and __delete__.

Detailed explanation of descriptors

Talk about Property

Above, we have given the Property implementation code, now let us 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

#----------------------------------------------------------------------
 @Property
deffull_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'

Copy after login

First of all, if you don’t know about decorators, you may want to read this article. In short, before we officially run the code, our interpreter will scan our code and perform a scan on the code involved. Replace the decorator part. The same goes for class decorators. In the above, this code

@Property
deffull_name(self):
"""
 Return the full name
 """
return"%s %s"% (self.first_name, self.last_name)
Copy after login

will trigger such a process, that is, full_name=Property(full_name). Then after we instantiate the object later, we call person.full_name. This process is actually equivalent to person.full_name.__get__(person) and then triggers the return self.fget(obj) written in the __get__() method, which is the original Above is the execution code in def full_name we wrote.

At this time, comrades can think about the specific operating mechanisms of getter(), setter(), and deleter()=. =If you still have questions, please feel free to discuss them in the comments.

About descriptors

Remember the definition we mentioned before: Descriptors are a special kind of object that implements three special methods: __get__, __set__, and __delete__. Then in the description of the official Python documentation, in order to reflect the importance of descriptors, there is this paragraph: "They are the mechanism 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 the descriptor first and then there is the sky, and the air comes next. Well, in new-style classes, attributes, method calls, static methods, class methods, etc. are all based on the specific use of descriptors.

OK, you may want to ask, why are descriptors so important? Don’t worry, let’s continue watching

Use descriptors

First, please look at the next piece of code

classA(object):#Note: In the Python 3.x version, there is no need to explicitly specify inheritance from the object class for the use of new class. If it is in the Python 2.X (x>2) version, it is required

defa(self):
pass
if__name__=="__main__":
 a=A()
 a.a()
Copy after login

Everyone has noticed that we have such a statement a.a(). Okay, now please think about it, what happens when we call this method?

OK? Have you figured it out yet? No? Okay let’s continue

First of all, when we call an attribute, whether it is a member or a method, we will trigger a method to call the attribute __getattribute__(). In our __getattribute__() method, if the attribute we try to call implements our Descriptor protocol, then such a calling process type(a).__dict__['a'].__get__(b,type(b)) will be generated. Okay, here we have to give another conclusion: "In such a call process, there is such a priority order. If the attribute we are trying to call is a data descriptor, then regardless of whether this attribute exists in the __dict__ of our instance In the dictionary, the __get__ method in our descriptor is called first. If the attribute we are trying to call is a non data descriptor, then we give priority to the existing attribute in __dict__ in our instance. If it does not exist, the corresponding principle is followed. Look up the attributes contained in the __dict__ of our class and the parent class. Once the attribute exists, the __get__ method is called. If it does not exist, the __getattr__() method is called." A bit abstract to understand? It’s okay, we’ll get to it soon, but here, we first need to explain data descriptors and non data descriptors, and then look at an example. What are data descriptors and non data descriptors? In fact, it is very simple. Descriptors that implement both the __get__ and __set__ protocols in the descriptor are data descriptors. If only the __get__ protocol is implemented, they are non data descriptors. Okay, let’s look at an example now:

importmath
classlazyproperty:
def__init__(self, func):
 self.func = func

def__get__(self, instance, owner):
ifinstanceisNone:
returnself
else:
 value = self.func(instance)
 setattr(instance, self.func.__name__, value)
returnvalue
classCircle:
def__init__(self, radius):
 self.radius = radius
pass

 @lazyproperty
defarea(self):
 print("Com")
returnmath.pi * self.radius *2

deftest(self):
pass
if__name__=='__main__':
 c=Circle(4)
 print(c.area)

Copy after login

好的,让我们仔细来看看这段代码,首先类描述符 @lazyproperty 的替换过程,前面已经说了,我们不在重复。接着,在我们第一次调用 c.area 的时候,我们首先查询实例 c 的 __dict__ 中是否存在着 area 描述符,然后发现在 c 中既不存在描述符,也不存在这样一个属性,接着我们向上查询 Circle 中的 __dict__ ,然后查找到名为 area 的属性,同时这是一个 non data descriptors ,由于我们的实例字典内并不存在 area 属性,那么我们便调用类字典中的 area 的 __get__ 方法,并在 __get__ 方法中通过调用 setattr 方法为实例字典注册属性 area 。紧接着,我们在后续调用 c.area 的时候,我们能在实例字典中找到 area 属性的存在,且类字典中的 area 是一个 non data descriptors ,于是我们不会触发代码里所实现的 __get__ 方法,而是直接从实例的字典中直接获取属性值。

描述符的使用

描述符的使用面很广,不过其主要的目的在于让我们的调用过程变得可控。因此我们在一些需要对我们调用过程实行精细控制的时候,使用描述符,比如我们之前提到的这个例子

classlazyproperty:
def__init__(self, func):
 self.func = func

def__get__(self, instance, owner):
ifinstanceisNone:
returnself
else:
 value = self.func(instance)
 setattr(instance, self.func.__name__, value)
returnvalue

def__set__(self, instance, value=0):
pass


importmath


classCircle:
def__init__(self, radius):
 self.radius = radius
pass

 @lazyproperty
defarea(self, value=0):
 print("Com")
ifvalue ==0andself.radius ==0:
raiseTypeError("Something went wring")

returnmath.pi * value *2ifvalue !=0elsemath.pi * self.radius *2

deftest(self):
pass

Copy after login

利用描述符的特性实现懒加载,再比如,我们可以控制属性赋值的值

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 = fdel
ifdocisNoneandfgetisnotNone:
 doc = fget.__doc__
 self.__doc__ = doc

def__get__(self, obj, objtype=None):
ifobjisNone:
returnself
ifself.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

 @Property
defValue(self):
returnself.value

 @Value.setter
deftest(self, x):
 self.value = x

Copy after login

如上面的例子所描述的一样,我们可以判断所传入的值是否有效等等。

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template