Python语言学习讲解十六:python之描述符__set__和__get__ 等解释

注:每周一到周五都会进行相关Python基础知识更新,欢迎大家提宝贵的意见

一、方法:

首先说下python中存在的几种方法:对象方法、静态方法、类方法等,归属权分别为obj、cls、cls

其实可以从他们的参数中就可以看的出来

对象方法参数中含有self,这个类似于C++中的this指针。

静态方法使用@staticmethod来修饰,可以通过类或类的实例对象来调用而已.

  1. >>> class Parent:  
  2.   
  3.     class_attr = "class_attr" #######类似于C++中类成员、方法,所有对象是共享内存,对象和类都是可以进行调用的。
  4.     def __init__(self, value):  
  5. self.obj_attr = value
  6.         print self.obj_attr 
  7.  
  8.     @staticmethod   #####静态方法
  9.     def static_fn(str):         
  10.         print "static_fnis call...say %s" % str  
  11.  
  12.  
  13.     @classmethod   #####类方法
  14.     def class_fn(cls, str):   
  15.         print "class_fnis call...say %s" % str    
  16.   
  17.     def obj_fn(self,str):   #####对象方法  
  18.          print "obj_fn is call...say %s" % str    
  19. >>> p=Parent('obj_attr')
  20. obj_attr
  21. >>> Parent.__dict__
    {'obj_fn': <function obj_fn at 0x02C44B70>, '__module__': '__main__', 'class_attr': 'class_attr', 'class_fn': <classmethod object at 0x02CB40F0>, 'static_fn': <staticmethod object at 0x02C36F50>, '__doc__': None, '__init__': <function __init__ at 0x02C44B30>} #####可以看到类中所包含的成员 
    >>> p.__dict__
    {'obj_attr': 'obj_attr'} #####可以看到对象中所包含的成员,类成员对象并没有在对象的dict中 
  22. >>> id(p.class_attr)
    46362560 #####在看用对象调用类成员的地址输出 
  23. >>> id(Parent.class_attr)
    46362560 #####在看用类调用类成员的地址输出,神奇的发现居然地址是一样的。那就是验证了上面所
  24. 说的对象其实都是使用类的对象的地址。所有对象在没有修改自己类对象成员的时候都是指向类的初始地址。
  25. >>> p.class_attr = "change_attr" #####我们使用对象修改了类成员属性,然后查看下具体的情况和地址变化
    >>> p.class_attr
    'change_attr' #####对象属性变化了,为何那???原因在于进行此操作的时候,会再对象中添加属于对象
  26. 的成员change_attr,而且是用新的内存地址,于类的相互不影响
    >>> Parent.class_attr
    'class_attr' #####属性无变化了
    >>> id(p.class_attr)
    46212960 #####内存地址变化了
    >>> id(Parent.class_attr)
    46362560
  27. >>> p.__dict__
    {'class_attr': 'change_attr', 'obj_attr': 'obj_attr'}#####成员新增了:change_attr
  28. >>> Parent.class_attr = "p_change_attr"
    >>> p.class_attr
    'change_attr' #####可见当对象的成员发生了地址新的变化后,修改类成员引用的地址,不会影响对象的成员。如果
  29. 对象此时没有自己的类成员空间,那么类修改类成员空间,则对象的类成员也会发生地址空间变化。


属性可以分为两类,一类是Python自动产生的,如__class__,__hash__等,另一类是我们自定义的,如上面的hello,name。我们只关心自定义属性。
类和实例对象(实际上,Python中一切都是对象,类是type的实例)都有__dict__属性,里面存放它们的自定义属性(对与类,里面还存放了别的东西)。

有些内建类型,如list和string,它们没有__dict__属性,所以没办法在它们上面附加自定义属性。


对于class_attr 属性Parent.class_attr Parent.__dict__['class_attr']是完全一样的。因为查找的地方就是__dict__字典中。默认__dict__省略了。没啥神奇的~~~

Python代码   收藏代码
  1. >>> Parent.class_attr
  2. 'p_change_attr'  
  3. >>> Parent.__dict__['class_attr']  
  4. 'p_change_attr'  
  5. >>>  

但是对于obj_fn,情形就有些不同了

Python代码   收藏代码
  1. >>> Parent.obj_fn
  2. <unbound method Parent.obj_fn>  
  3. >>> Parent.__dict__['obj_fn']  
  4. <function obj_fn at 0x00C3AD70>  
  5. >>>   

可以发现,Parent.obj_fn是个unbound method。而Parent.__dict__['obj_fn']是个函数(不是方法)。

推断:方法在类的__dict__中是以函数的形式存在的(方法的定义和函数的定义简直一样,除了要把第一个参数设为self)。那么Parent.obj_fn针对的是对象的调用。

  1. >>> p.obj_fn
    <bound method Parent.obj_fn of <__main__.Parent instance at 0x02C2EE90>>

是一个bound method。

关于unbound和bound到还好理解,我们不妨先作如下设想:方法是要从实例调用的嘛(指实例方法,classmethod和staticmethod后面讲),如果从类中访问,如Parent.obj_fnobj_fn没有和任何实例发生联系,也就是没绑定(unbound)到任何实例上,所以是个unbound,对
  1. p.obj_fn 的访问方式,obj_fn和p发生了联系,因此是bound。

一切的魔法都源自今天的主角:descriptor

 

查找属性时,如p.class_attr,如果Python发现这个属性class_attr有个__get__方法,Python会调用class_attr的__get__方法,返回__get__方法的返回值,而不是返回class_attr(这一句话并不准确,我只是希望你能对descriptor有个初步的概念)。

Python中iterator(怎么扯到Iterator了?)是实现了iterator协议的对象,也就是说它实现了下面两个方法__iter__和next()。类似的,descriptor也是实现了某些特定方法的对象。descriptor的特定方法是__get__,__set__和__delete__,其中__set__和__delete__方法是可选的。iterator必须依附某个对象而存在(由对象的__iter__方法返回),descriptor也必须依附对象,作为对象的一个属性,它而不能单独存在。还有一点,descriptor必须存在于类的__dict__中,这句话的意思是只有在类的__dict__中找到属性,Python才会去看看它有没有__get__等方法,对一个在实例的__dict__中找到的属性,Python根本不理会它有没有__get__等方法,直接返回属性本身。descriptor到底是什么呢:简单的说,descriptor是对象的一个属性,只不过它存在于类的__dict__中并且有特殊方法__get__(可能还有__set__和__delete)而具有一点特别的功能,为了方便指代这样的属性,我们给它起了个名字叫descriptor属性。

Python代码   收藏代码
  1. class Descriptor(object):  
  2.     def __get__(self, obj, type=None):  
  3.             return 'get'self, obj, type  
  4.     def __set__(self, obj, val):  
  5.         print 'set'self, obj, val  
  6.     def __delete__(self, obj):  
  7.         print 'delete'self, obj  

这里__set__和__delete__其实可以不出现,不过为了后面的说明,暂时把它们全写上。

下面解释一下三个方法的参数:

self当然不用说,指的是当前Descriptor的实例。obj值拥有属性的对象。这应该不难理解,前面已经说了,descriptor是对象的稍微有点特殊的属性,这里的obj就是拥有它的对象,要注意的是,如果是直接用类访问descriptor(别嫌啰嗦,descriptor是个属性,直接用类访问descriptor就是直接用类访问类的属性),obj的值是None。type是obj的类型,刚才说过,如果直接通过类访问descriptor,obj是None,此时type就是类本身。

三个方法的意义,假设T是一个类,t是它的一个实例,d是T的一个descriptor属性(牛什么啊,不就是有个__get__方法吗!),value是一个有效值:

读取属性时,如T.d,返回的是d.__get__(None, T)的结果,t.d返回的是d.__get__(t, T)的结果。

设置属性时,t.d = value,实际上调用d.__set__(t, value),T.d = value,这是真正的赋值,T.d的值从此变成value。删除属性和设置属性类似。

是时候坦白真正详细的属性查找策略 了,对于obj.attr(注意:obj可以是一个类):

1.如果attr是一个Python自动产生的属性,找到!(优先级非常高!)

2.查找obj.__class__.__dict__,如果attr存在并且是data descriptor,返回data descriptor的__get__方法的结果,如果没有继续在obj.__class__的父类以及祖先类中寻找data descriptor

3.在obj.__dict__中查找,这一步分两种情况,第一种情况是obj是一个普通实例,找到就直接返回,找不到进行下一步。第二种情况是obj是一个类,依次在obj和它的父类、祖先类的__dict__中查找,如果找到一个descriptor就返回descriptor的__get__方法的结果,否则直接返回attr。如果没有找到,进行下一步。

4.在obj.__class__.__dict__中查找,如果找到了一个descriptor(插一句:这里的descriptor一定是non-data descriptor,如果它是data descriptor,第二步就找到它了)descriptor的__get__方法的结果。如果找到一个普通属性,直接返回属性值。如果没找到,进行下一步。

5.很不幸,Python终于受不了。在这一步,它raise AttributeError


首先,Python判断name属性是否是个自动产生的属性,如果是自动产生的属性,就按特别的方法找到这个属性,当然,这里的name不是自动产生的属性,而是我们自己定义的,Python于是到t的__dict__中寻找。还是没找到。

接着,Python找到了t所属的类T,搜索T.__dict__,期望找到name,很幸运,直接找到了,于是返回name的值:字符串‘name’。如果在T.__dict__中还没有找到,Python会接着到T的父类(如果T有父类的话)的__dict__中继续查找。


总结:

只要记住类中的成员变量是对象的时候,对象又是个修饰器的时候。那么对成员变量的赋值实际是调用他的get和set方法就O了。参数很简单。


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值