Python 描述符(Descriptor)入门

很久都没写 Flask 代码相关了,想想也真是惭愧,然并卵,这次还是不写 Flask 相关,不服你来打我啊(就这么贱,有本事咬我啊

这次我来写一下 Python 一个很重要的东西,即 Descriptor (描述符)

初识描述符

老规矩, Talk is cheap,Show me the code. 我们先来看看一段代码

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'

这段代大家肯定很熟悉,恩, property 嘛,谁不知道呢,但是 property 的实现机制大家清楚么?什么不清楚?那还学个毛的 Python 啊。。。开个玩笑,我们看下面一段代码

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__)

看起来是不是很复杂,没事,我们来一步步的看。不过这里我们首先给出一个结论: Descriptors 是一种特殊 的对象,这种对象实现了 __get__ , __set__ , __delete__ 这三个特殊方法。

详解描述符

说说 Property

在上文,我们给出了 Propery 实现代码,现在让我们来详细说说这个

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'

首先,如果你对装饰器不了解的话,你可能要去看看这篇文章,简而言之,在我们正式运行代码之前,我们的解释器就会对我们的代码进行一次扫描,对涉及装饰器的部分进行替换。类装饰器同理。在上文中,这段代码

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

会触发这样一个过程,即 full_name=Property(full_name) 。然后在我们后面所实例化对象之后我们调用 person.full_name 这样一个过程其实等价于 person.full_name.__get__(person) 然后进而触发 __get__() 方法里所写的 return self.fget(obj) 即原本上我们所编写的 def full_name 内的执行代码。

这个时候,同志们可以去思考下 getter() , setter() ,以及 deleter() 的具体运行机制了=。=如果还是有问题,欢迎在评论里进行讨论。

关于描述符

还记得之前我们所提到的一个定义么: Descriptors 是一种特殊的对象,这种对象实现了 __get__ , __set__ , __delete__ 这三个特殊方法 。然后在 Python 官方文档的说明中,为了体现描述符的重要性,有这样一段话:“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. ” 简而言之就是 先有描述符后有天,秒天秒地秒空气 。恩,在新式类中,属性,方法调用,静态方法,类方法等都是基于描述符的特定使用。

OK,你可能想问,为什么描述符是这么重要呢?别急,我们接着看

使用描述符

首先请看下一段代码

classA(object):#注:在 Python 3.x 版本中,对于 new class 的使用不需要显式的指定从 object 类进行继承,如果在 Python 2.X(x>2)的版本中则需要

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

大家都注意到了我们存在着这样一个语句 a.a() ,好的,现在请大家思考下,我们在调用这个方法的时候发生了什么?

OK?想出来了么?没有?好的我们继续

首先我们调用一个属性的时候,不管是成员还是方法,我们都会触发这样一个方法用于调用属性 __getattribute__() ,在我们的 __getattribute__() 方法中,如果我们尝试调用的属性实现了我们的描述符协议,那么会产生这样一个调用过程 type(a).__dict__['a'].__get__(b,type(b)) 。好的这里我们又要给出一个结论了:“在这样一个调用过程中,有这样一个优先级顺序,如果我们所尝试调用属性是一个 data descriptors ,那么不管这个属性是否存在我们的实例的 __dict__ 字典中,优先调用我们描述符里的 __get__ 方法,如果我们所尝试调用属性是一个 non data descriptors ,那么我们优先调用我们实例里的 __dict__ 里的存在的属性,如果不存在,则依照相应原则往上查找我们类,父类中的 __dict__ 中所包含的属性,一旦属性存在,则调用 __get__ 方法,如果不存在则调用 __getattr__() 方法”。理解起来有点抽象?没事,我们马上会讲,不过在这里,我们先要解释下 data descriptors 与 non data descriptors ,再来看一个例子。什么是 data descriptors 与 non data descriptors 呢?其实很简单,在描述符中同时实现了 __get__ 与 __set__ 协议的描述符是 data descriptors ,如果只实现了 __get__ 协议的则是 non data descriptors 。好了我们现在来看个例子:

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)

好的,让我们仔细来看看这段代码,首先类描述符 @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

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

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

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

(0)

相关推荐

  • Python黑魔法Descriptor描述符的实例解析

    在Python中,访问一个属性的优先级顺序按照如下顺序: 1:类属性 2:数据描述符 3:实例属性 4:非数据描述符 5:__getattr__()方法  这个方法的完整定义如下所示: def __getattr(self,attr) :#attr是self的一个属性名 pass; 先来阐述下什么叫数据描述符. 数据描述符是指实现了__get__,__set__,__del__方法的类属性(由于Python中,一切皆是对象,所以你不妨把所有的属性也看成是对象) PS:个人觉得这里最好把数据描述符

  • 解密Python中的描述符(descriptor)

    Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些"中级"的语言特性有着完善的文档,并且易于学习. 但是这里有个例外,那就是描述符.至少对于我来说,描述符是Python语言核心中困扰我时间最长的一个特性.这里有几点原因如下: 1.有关描述符的官方文档相当难懂,而且没有包含优秀的示例告诉你为什么需要编写描述符(我得为Raymond Hettinger辩

  • Python中的类与对象之描述符详解

    描述符(Descriptors)是Python语言中一个深奥但却重要的一部分.它们广泛应用于Python语言的内核,熟练掌握描述符将会为Python程序员的工具箱添加一个额外的技巧.为了给接下来对描述符的讨论做一些铺垫,我将描述一些程序员可能会在日常编程活动中遇到的场景,然后我将解释描述符是什么,以及它们如何为这些场景提供优雅的解决方案.在这篇总结中,我会使用新样式类来指代Python版本. 1.假设一个程序中,我们需要对一个对象属性执行严格的类型检查.然而,Python是一种动态语言,所以并不

  • Python 的描述符 descriptor详解

    Python 在 2.2 版本中引入了descriptor(描述符)功能,也正是基于这个功能实现了新式类(new-styel class)的对象模型,同时解决了之前版本中经典类 (classic class) 系统中出现的多重继承中的 MRO(Method Resolution Order) 问题,另外还引入了一些新的概念,比如 classmethod, staticmethod, super, Property 等.因此理解 descriptor 有助于更好地了解 Python 的运行机制.

  • Python中属性和描述符的正确使用

    关于@property装饰器 在Python中我们使用@property装饰器来把对函数的调用伪装成对属性的访问. 那么为什么要这样做呢?因为@property让我们将自定义的代码同变量的访问/设定联系在了一起,同时为你的类保持一个简单的访问属性的接口. 举个栗子,假如我们有一个需要表示电影的类: class Movie(object): def __init__(self, title, description, score, ticket): self.title = title self.

  • python的描述符(descriptor)、装饰器(property)造成的一个无限递归问题分享

    分享一下刚遇到的一个小问题,我有一段类似于这样的python代码: 复制代码 代码如下: # coding: utf-8 class A(object): @property     def _value(self): #        raise AttributeError("test")         return {"v": "This is a test."} def __getattr__(self, key):         p

  • Python描述符descriptor使用原理解析

    描述符(descriptor)是实现了__get__.__set__.__del__方法的类,进一步可以细分为两类: 数据描述符:实现了__get__和__set__ 非数据描述符:没有实现__set__ 描述符在类的属性调用中起着很重要的作用,类在调用属性时,遵守两个规则: 按照实例属性.类属性的顺序选择属性,即实例属性优先于类属性 如果在类属性中发现同名的数据描述符,那么该描述符会优先于实例属性 非数据描述符会被实例属性覆盖 class A: def __get__(self, obj, c

  • 通过实例解析python描述符原理作用

    这篇文章主要介绍了通过实例解析python描述符原理作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本质上看,描述符是一个类,只不过它定义了另一个类中属性的访问方式.换句话说,一个类可以将属性管理全权委托给描述符类. 描述符类基于以下三种特殊方法,换句话说,这三种方法组成了描述符协议: __set__(self, obj, type = None): 在设置属性时,将调用这一方法. __get__(self, obj, value): 在读

  • 详解Python描述符的工作原理

    一.前言 其实,在开发过程中,虽然我们没有直接使用到描述符,但是它在底层却无时不刻地被使用到,例如以下这些: function.bound method.unbound method 装饰器property.staticmethod.classmethod 是不是都很熟悉? 这些都与描述符有着千丝万缕的关系,这篇文章我们就来看一下描述符背后的工作原理. 二.什么是描述符? 在解释什么是「描述符」之前,我们先来看一个简单的例子. 这个例子非常简单,我们在类 A 中定义了一个类属性 x,然后打印它的

  • Python 描述符(Descriptor)入门

    很久都没写 Flask 代码相关了,想想也真是惭愧,然并卵,这次还是不写 Flask 相关,不服你来打我啊(就这么贱,有本事咬我啊 这次我来写一下 Python 一个很重要的东西,即 Descriptor (描述符) 初识描述符 老规矩, Talk is cheap,Show me the code. 我们先来看看一段代码 classPerson(object): """""" #---------------------------------

  • Python描述器descriptor详解

    前面说了descriptor,这个东西其实和Java的setter,getter有点像.但这个descriptor和上文中我们开始提到的函数方法这些东西有什么关系呢? 所有的函数都可以是descriptor,因为它有__get__方法. 复制代码 代码如下: >>> def hello():      pass  >>> dir(hello)  ['__call__', '__class__', '__delattr__', '__dict__', '__doc__',

  • Python descriptor(描述符)的实现

    问题 问题1 Python是一种动态语言,不支持类型检查.当需要对一个对象执行类型检查时,可能会采用下面的方式: class Foo(object): def __init__(self,a): if isinstance(a,int): self.__a = a else: raise TypeError("Must be an int") def set_a(self,val): if isinstance(val,int): self.__a = val else: raise

  • python实现装饰器、描述符

    概要 本人python理论知识远达不到传授级别,写文章主要目的是自我总结,并不能照顾所有人,请见谅,文章结尾贴有相关链接可以作为补充 全文分为三个部分装饰器理论知识.装饰器应用.装饰器延申 装饰理基础:无参装饰器.有参装饰器.functiontools.装饰器链 装饰器进阶:property.staticmethod.classmethod源码分析(python代码实现) 装饰器基础 无参装饰器 ''' 假定有一个需求是:打印程序函数运行顺序 此案例打印的结果为: foo1 function i

随机推荐