您的位置:首页 > 编程语言 > Python开发

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

2016-11-30 16:03 453 查看
注:每周一到周五都会进行相关Python基础知识更新,欢迎大家提宝贵的意见

一、方法:

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

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

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

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

>>> class Parent:  

  

    class_attr = "class_attr"
#######类似于C++中类成员、方法,所有对象是共享内存,对象和类都是可以进行调用的。

    def __init__(self, value):  

self.obj_attr = value

        print self.obj_attr 

 

    @staticmethod  
#####静态方法

    def static_fn(str):  
      

        print "static_fnis
call...say %s" % str  

 

 

    @classmethod  

#####类方法

    def class_fn(cls, str):
  

        print "class_fnis
call...say %s" %
str    

  

    def obj_fn(self,str):  
#####对象方法  

         print "obj_fn is
call...say %s" % str    

>>> p=Parent('obj_attr')

obj_attr

>>> 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中 

>>> id(p.class_attr)
46362560
#####在看用对象调用类成员的地址输出 

>>> id(Parent.class_attr)
46362560
#####在看用类调用类成员的地址输出,神奇的发现居然地址是一样的。那就是验证了上面所

说的对象其实都是使用类的对象的地址。所有对象在没有修改自己类对象成员的时候都是指向类的初始地址。

>>>
p.class_attr = "change_attr"
#####我们使用对象修改了类成员属性,然后查看下具体的情况和地址变化

>>> p.class_attr
'change_attr'
#####对象属性变化了,为何那???原因在于进行此操作的时候,会再对象中添加属于对象

的成员change_attr,而且是用新的内存地址,于类的相互不影响
>>> Parent.class_attr
'class_attr'
#####类属性无变化了
>>> id(p.class_attr)
46212960
#####内存地址变化了

>>> id(Parent.class_attr)
46362560

>>>
p.__dict__
{'class_attr': 'change_attr', 'obj_attr': 'obj_attr'}#####成员新增了:change_attr

>>>
Parent.class_attr = "p_change_attr"

>>> p.class_attr
'change_attr'
#####可见当对象的成员发生了地址新的变化后,修改类成员引用的地址,不会影响对象的成员。如果

对象此时没有自己的类成员空间,那么类修改类成员空间,则对象的类成员也会发生地址空间变化。

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

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

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

Python代码  



>>> Parent.class_attr

'p_change_attr'  

>>> Parent.__dict__['class_attr']  

'p_change_attr'  

>>>  

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

Python代码  



>>> Parent.obj_fn

<unbound method Parent.obj_fn>  

>>> Parent.__dict__['obj_fn']  

<function obj_fn at 0x00C3AD70>  

>>>   



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

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

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

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

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代码  



class Descriptor(object):  

    def __get__(self, obj, type=None):  

            return 'get', self, obj, type  

    def __set__(self, obj, val):  

        print 'set', self, obj, val  

    def __delete__(self, obj):  

        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了。参数很简单。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息