Python 描述符(Descriptor)入门


Posted in Python onNovember 20, 2016

很久都没写 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

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

Python 相关文章推荐
python2.7删除文件夹和删除文件代码实例
Dec 18 Python
Python中使用hashlib模块处理算法的教程
Apr 28 Python
基于Python实现一个简单的银行转账操作
Mar 06 Python
python字符串,数值计算
Oct 05 Python
python中文乱码不着急,先看懂字节和字符
Dec 20 Python
Python通过TensorFlow卷积神经网络实现猫狗识别
Mar 14 Python
详解Python 定时框架 Apscheduler原理及安装过程
Jun 14 Python
对Django项目中的ORM映射与模糊查询的使用详解
Jul 18 Python
深入了解Python enumerate和zip
Jul 16 Python
python实现三壶谜题的示例详解
Nov 02 Python
关于PySnooper 永远不要使用print进行调试的问题
Mar 04 Python
python Django框架快速入门教程(后台管理)
Jul 21 Python
独特的python循环语句
Nov 20 #Python
【Python】Python的urllib模块、urllib2模块批量进行网页下载文件
Nov 19 #Python
Python基础中所出现的异常报错总结
Nov 19 #Python
轻松掌握python设计模式之策略模式
Nov 18 #Python
轻松掌握python设计模式之访问者模式
Nov 18 #Python
Win10下Python环境搭建与配置教程
Nov 18 #Python
Python Paramiko模块的安装与使用详解
Nov 18 #Python
You might like
解决中英文字符串长度问题函数
2007/01/16 PHP
PHP has encountered a Stack overflow问题解决方法
2014/11/03 PHP
基于jquery可配置循环左右滚动例子
2011/09/09 Javascript
jQuery $.get 的妙用 访问本地文本文件
2012/07/12 Javascript
Extjs中TabPane如何嵌套在其他网页中实现思路及代码
2013/01/27 Javascript
jQuery焦点图切换简易插件制作过程全纪录
2014/08/27 Javascript
JavaScript实现仿网易通行证表单验证
2015/05/25 Javascript
基于JavaScript实现根据手机定位获取当前具体位置(X省X市X县X街道X号)
2015/12/29 Javascript
全面解析Bootstrap中scrollspy(滚动监听)的使用方法
2016/06/06 Javascript
jQuery快速高效制作网页交互特效
2017/02/24 Javascript
Angularjs 与 bower安装和使用详解
2017/05/11 Javascript
JS回调函数基本定义与用法实例分析
2017/05/24 Javascript
node使用UEditor富文本编辑器的方法实例
2017/07/11 Javascript
VueJs单页应用实现微信网页授权及微信分享功能示例
2017/07/26 Javascript
ES6学习教程之Map的常用方法总结
2017/08/03 Javascript
详解Vue.js Mixins 混入使用
2017/09/15 Javascript
使用D3.js+Vue实现一个简单的柱形图
2018/08/05 Javascript
vue配置接口域名方法总结
2019/05/12 Javascript
基于jquery实现的tab选项卡功能示例【附源码下载】
2019/06/10 jQuery
Vue源码分析之Vue实例初始化详解
2019/08/25 Javascript
vue封装可复用组件confirm,并绑定在vue原型上的示例
2019/10/31 Javascript
Python Xml文件添加字节属性的方法
2018/03/31 Python
python实现扫描局域网指定网段ip的方法
2019/04/16 Python
Python 中使用 PyMySQL模块操作数据库的方法
2019/11/10 Python
记录一下scrapy中settings的一些配置小结
2020/09/28 Python
Python基于callable函数检测对象是否可被调用
2020/10/16 Python
任意存:BOXFUL
2018/05/21 全球购物
HEMA英国:荷兰原创设计
2018/08/28 全球购物
中专三年学习的个人自我评价
2013/12/12 职场文书
设备管理实施方案
2014/05/31 职场文书
2015年客服工作总结范文
2015/04/02 职场文书
市场督导岗位职责
2015/04/10 职场文书
海底两万里读书笔记
2015/06/26 职场文书
三好学生竞选稿
2015/11/21 职场文书
优秀创业计划书分享
2019/07/19 职场文书
Java对文件的读写操作方法
2022/04/29 Java/Android