设为首页 收藏本站
查看: 2946|回复: 0

[经验分享] Python学习笔记——描述符

[复制链接]
发表于 2015-4-20 11:41:03 | 显示全部楼层 |阅读模式
  在Python中,访问一个属性的优先级顺序按照如下顺序:


  • 类属性
  • 数据描述符
  • 实例属性
  • 非数据描述符
  • __getattr__()方法  这个方法的完整定义如下所示:

     
def __getattr(self,attr) :#attr是self的一个属性名
pass;

  先来阐述下什么叫数据描述符。
  数据描述符是指实现了__get__,__set__,__del__方法的类属性(由于Python中,一切皆是对象,所以你不妨把所有的属性也看成是对象)
  PS:个人觉得这里最好把数据描述符等效于定义了__get__,__set__,__del__三个方法的接口。
  阐述下这三个方法:
  __get__的标准定义是__get__(self,obj,type=None),它非常接近于JavaBean的get
  第一个函数是调用它的实例,obj是指去访问属性所在的方法,最后一个type是一个可选参数,通常为None(这个有待于进一步的研究)
  例如给定类X和实例x,调用x.foo,等效于调用:

      
type(x).__dict__["foo"].__get__(x,type(x))

  调用X.foo,等效于调用:

     
type(x).__dict__["foo"].__get__(None,type(x))

  第二个函数__set__的标准定义是__set__(self,obj,val),它非常接近于JavaBean的set方法,其中最后一个参数是要赋予的值
  第三个函数__del__的标准定义是__del__(self,obj),它非常接近Java中Object的Finailize()方法,指Python在回收这个垃圾对象时所调用到的析构函数,只是这个函数永远不会抛出异常。因为这个对象已经没有引用指向它,抛出异常没有任何意义。
  接下来,我们来一一比较这些优先级.
  首先来看类属性

     
class A(object):
foo=1.3;
print str(A.__dict__);

  输出:

     
{"__dict__": , "__module__": "__main__",
"foo": 1.3, "__weakref__":  , "__doc__": None}

  从上图可以看出foo属性在类的__dict__属性里,所以这里用A.foo可以直接找到。这里我们先跨过数据描述符,直接来看实例属性。

     
class A(object):
foo=1.3;
a=A();
print a.foo;
a.foo=15;
print a.foo;  

  这里a.foo先输出1.3后输出15,不是说类属性的优先级比实例属性的优先级高吗?按理a.foo应该不变才对?其实,这里只是一个假象,真正的原因在于这里将a.foo这个引用对象,不妨将其理解为可以指向任意数据类型的指针,指向了15这个int对象。
  不信,可以继续看:

   
class A(object):
foo=1.3;
a=A();
print a.foo;
a.foo=15;
print a.foo;
del a.foo;
print a.foo;  

  这次在输出1.3,15后最后一次又一次的输出了1.3,原因在于a.foo最后一次又按照优先级顺序直接找到了类属性A.foo
  然后我们来看下数据描述符这一全新的语言概念。按照之前的定义,一个实现了__get__,__set__,__del__的类都统称为数据描述符。我们来看下一个简单的例子。

   
class simpleDescriptor(object):
def __get__(self,obj,type=None) :
pass;
def __set__(self,obj,val):
pass;
def __del__(self,obj):
pass
class A(object):
foo=simpleDescriptor();
print str(A.__dict__);
print A.foo;
a=A();
print a.foo;
a.foo=13;
print a.foo;

  这里get,set,del方法体内容都略过,虽然简单,但也不失为一个数据描述符。让我们来看下它的输出:

   
{"__dict__":  , "__module__": "__main__",
"foo":  ,
"__weakref__":  ,
"__doc__": None}
None
None
None

  从上图可以看出,尽管我们对a.foo赋值了,但其依然为None,原因就在于__get__方法什么都不返回。
  为了更进一步的加深对数据描述符的理解,我们简单的作下改造。

   
class simpleDescriptor(object):
def __init__(self):
self.result=None;
def __get__(self,obj,type=None) :
return self.result-10;
def __set__(self,obj,val):
self.result=val+3;
print self.result;
def __del__(self,obj):
pass
class A(object):
foo=simpleDescriptor();
a=A();
a.foo=13;
print a.foo;

  打印的输出结果为:

   
16
6

  第一个16为我们在对a.foo赋值的时候,人为的将13加上3后作为foo的值,第二个6是我们在返回a.foo之前人为的将它减去了10。
  所以我们可以猜测,常规的Python类在定义get,set方法的时候,如果无特殊需求,直接给对应的属性赋值或直接返回该属性值。如果自己定义类,并且继承object类的话,这几个方法都不用定义。
  下面我们来看下实例属性和非数据描述符。

     
class B(object):
foo=1.3;
b=B();
print b.__dict__
#print b.bar;
b.bar=13;
print b.__dict__
print b.bar;

输出结果为:
{}
{"bar": 13}
13
  可见这里在实例b.__dict__里找到了bar属性,所以这次可以获取13了。
  那么什么是非数据描述符呢?简单的说,就是没有实现get,set,del三个方法的所有类
  让我们任意看一个函数的描述:

     
def hello():
pass
print dir(hello)

  输出:

     
["__call__", "__class__", "__delattr__", "__dict__",
"__doc__",
"__get__",
"__getattribute__",
"__hash__", "__init__", "__module__", "__name__",
"__new__", "__reduce__",
"__reduce_ex__", "__repr__",
"__setattr__", "__str__", "func_closure",
"func_code",
"func_defaults", "func_dict", "func_doc", "func_globals", "func_name"]

  从上面可以看出所有的函数都有get方法,但都没有set和del方法,所以所有的类成员函数都是非数据描述符。
  看一个简单的例子:

     
class simpleDescriptor(object):   
def __get__(self,obj,type=None) :
return "get",self,obj,type;
class D(object):
foo=simpleDescriptor();
d=D();
print d.foo;
d.foo=15;
print d.foo;

  输出:

     
("get",  ,
,  )
15

  可以看出实例属性掩盖了非数据描述符。
  最后看下__getatrr__方法。它的标准定义是:__getattr__(self,attr),其中attr是属性名
  让我们来看一个简单的例子:

     
class D(object):
def __getattr__(self,attr):
return attr;
#return self.attr;
d=D();
print d.foo,type(d.foo);
d.foo=15;
print d.foo;

  输出:

     
foo
15

  可以看的出来Python在实在找不到方法的时候,就会求助于__getattr__方法。这有点像javascript中FF的私有实现__noSuchMethod__,或ruby中的method_missing.
  注意这里要避免无意识的递归,稍微改动下:

     
class D(object):
def __getattr__(self,attr):
#return attr;
return self.attr;
d=D();
print d.foo,type(d.foo);
d.foo=15;
print d.foo;

  这次会直接抛出堆栈溢出的异常,就像下面这样:

     
RuntimeError: maximum recursion depth exceeded

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-58824-1-1.html 上篇帖子: Python调用C/C++的种种方法 下篇帖子: 用Python复习离散数学(一)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表