python描述符

python描述符

在Python中,访问一个属性的优先级顺序按照如下顺序:
1.类属性
2.数据描述符  :__get__,__set__,__del__方法的类属性
3.实例属性
4.非数据描述符 :没有实现get,set,del三个方法的所有类
5.__getattr__()方法。这个方法的完整定义如下所示:
[python] view plaincopy
  1. def __getattr__(self,attr) :#attr是self的一个属性名   
  2.      
     
    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在回收这个垃圾对象时所调用到的析构函数,只是这个函数永远不会抛出异常。因为这个对象已经没有引用指向它,抛出异常没有任何意义。

接下来,我们来一一比较这些优先级.

首先来看类属性

[python] view plaincopy
  1. # -*- coding:utf-8 -*-   
  2.  
    ''''' 
  3. Created on 2013-3-29 
  4.   
  5. @author: naughty 
  6. '''  
  7.  
    class A(object):  
  8.     foo=
     
    3  
  9.   
  10.  
    print A.foo  
  11. a=A()  
  12.  
    print a.foo  
  13. a.foo=
     
    4  
  14.  
    print a.foo  
  15.  
    print A.foo  

上面这段代码的输出如下:

3
3
4
3

从输出可以看到,当我们给a.foo赋值的时候,其实与类实例的那个foo是没有关系的。a.foo=4 这句话给a对象增加了一个属性叫foo。其值是4。

最后两个语句明确的表明了,我们输出a.foo和A.foo的值,他们是不同的。

但是为什么a=A()语句后面的print a.foo输出了3呢?这是因为根据搜索顺序找到了类属性。当我们执行a.foo=4的时候,我们让a对象的foo属性指向了4这个对象。但是并没有改变类属性foo的值。所以最后我们print A.foo的时候,又输出了3。

[python] view plaincopy
  1. # -*- coding:utf-8 -*-   
  2.  
    ''''' 
  3. Created on 2013-3-29 
  4.   
  5. @author: naughty 
  6. '''  
  7.  
    class A(object):  
  8.     foo=
     
    3  
  9.   
  10. a=A()  
  11. a.foo=
     
    4  
  12.  
    print a.foo  
  13.  
    del a.foo  
  14.  
    print a.foo  

上面的代码,我给a.foo赋值为4,在输出一次之后就del了。两次输出,第一次输出的是a对象的属性。第二次是类属性。不是说类属性的优先级比实例属性的高吗。为啥第一次输出的是4而不是3呢?还是上面解释的原因。因为a.foo与类属性的foo只是重名而已。我们print a.foo的时候,a的foo指向的是4,所以输出了4。

------------------------------------

然后我们来看下数据描述符这一全新的语言概念。按照之前的定义,一个实现了__get__,__set__,__del__的类都统称为数据描述符。我们来看下一个简单的例子。

[python] view plaincopy
  1. # -*- coding:utf-8 -*-   
  2.  
    ''''' 
  3. Created on 2013-3-29 
  4.   
  5. @author: naughty 
  6. '''  
  7.   
  8.  
    class simpleDescriptor(object):  
  9.   
  10.    
     
    def __get__(self,obj,type=None):  
  11.        
     
    pass  
  12.   
  13.    
     
    def __set__(self,obj,val):  
  14.        
     
    pass  
  15.   
  16.    
     
    def __del__(self,obj):  
  17.        
     
    pass  
  18.   
  19.  
    class A(object):  
  20.     foo=simpleDescriptor()  
  21.       
  22.  
    print str(A.__dict__)  
  23.  
    print A.foo  
  24. a=A()  
  25.  
    print a.foo  
  26. a.foo=
     
    13  
  27.  
    print a.foo  

上面例子的输出结果如下:

  1. {'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 'foo': <__main__.simpleDescriptor object at 0x005511B0>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}  
  2. None  
  3. None  
  4. None  

从输出结果看出,print语句打印出来的都是None。这说明a.foo都没有被赋值内容。这是因为__get__函数的函数体什么工作都没有做。直接是pass。此时,想要访问foo,每次都没有返回内容,所以输出的内容就是None了。

[python] view plaincopy
  1. # -*- coding:utf-8 -*-   
  2.  
    ''''' 
  3. Created on 2013-3-29 
  4.   
  5. @author: naughty 
  6. '''  
  7.   
  8.  
    class simpleDescriptor(object):  
  9.   
  10.    
     
    def __get__(self,obj,type=None):  
  11.        
     
    return "hi there"  
  12.   
  13.    
     
    def __set__(self,obj,val):  
  14.        
     
    pass  
  15.   
  16.    
     
    def __del__(self,obj):  
  17.        
     
    pass  
  18.      
  19.  
    class A(object):  
  20.     foo=simpleDescriptor()  
  21.       
  22.  
    print str(A.__dict__)  
  23.  
    print A.foo  
  24. a=A()  
  25.  
    print a.foo  
  26. a.foo=
     
    13  
  27.  
    print a.foo  

把__get__函数实现以下,就可以得到如下输出结果:

  1. {'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 'foo': <__main__.simpleDescriptor object at 0x00671190>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}  
  2. hi there  
  3. hi there  
  4. hi there  

为了加深对数据描述符的理解,看如下例子:

[python] view plaincopy
  1. # -*- coding:utf-8 -*-   
  2.  
    ''''' 
  3. Created on 2013-3-29 
  4.   
  5. @author: naughty 
  6. '''  
  7.   
  8.  
    class simpleDescriptor(object):  
  9.     
     
    def __init__(self):  
  10.         
     
    self.result = None;  
  11.     
     
    def __get__(self, obj, type=None) :  
  12.         
     
    return self.result - 10;  
  13.     
     
    def __set__(self, obj, val):  
  14.         
     
    self.result = val + 3;  
  15.         
     
    print self.result;  
  16.     
     
    def __del__(self, obj):  
  17.         
     
    pass  
  18.  
    class A(object):  
  19.     foo = simpleDescriptor();  
  20. a = A();  
  21. a.foo = 
     
    13;  
  22.  
    print a.foo;  

上面代码的输出是

16

6

第一个16为我们在对a.foo赋值的时候,人为的将13加上3后作为foo的值,第二个6是我们在返回a.foo之前人为的将它减去了10。

所以我们可以猜测,常规的Python类在定义get,set方法的时候,如果无特殊需求,直接给对应的属性赋值或直接返回该属性值。如果自己定义类,并且继承object类的话,这几个方法都不用定义。

-----------------

在这里看一个题外话。

看代码

[python] view plaincopy
  1. # -*- coding:utf-8 -*-   
  2.  
    ''''' 
  3. Created on 2013-3-29 
  4.   
  5. @author: naughty 
  6. '''  
  7.   
  8.  
    class simpleDescriptor(object):  
  9.     
     
    def __init__(self):  
  10.         
     
    self.result = None;  
  11.     
     
    def __get__(self, obj, type=None) :  
  12.         
     
    return self.result - 10;  
  13.     
     
    def __set__(self, obj, val):  
  14.         
     
    if isinstance(val,str):  
  15.             
     
    assert False,"int needed! but get str"  
  16.         
     
    self.result = val + 3;  
  17.         
     
    print self.result;  
  18.     
     
    def __del__(self, obj):  
  19.         
     
    pass  
  20.  
    class A(object):  
  21.     foo = simpleDescriptor();  
  22. a = A();  
  23. a.foo = 
     
    "13";  
  24.  
    print a.foo;  

上面代码在__set__ 函数中检查了参数val,如果val是str类型的,那么要报错。这就实现了我们上一篇文章中要实现的,在给属性赋值的时候做类型检查的功能。

-----------------------------------------------

下面我们来看下实例属性和非数据描述符。

[python] view plaincopy
  1. # -*- coding:utf-8 -*-   
  2.  
    ''''' 
  3. Created on 2013-3-29 
  4.   
  5. @author: naughty 
  6. '''  
  7.   
  8.  
    class B(object):  
  9.     foo = 
     
    1.3  
  10. b = B()  
  11.  
    print b.__dict__  
  12. b.bar = 
     
    13  
  13.  
    print b.__dict__  
  14.  
    print b.bar  

上面代码输出结果如下:

 {}
{'bar': 13}
13

那么什么是非数据描述符呢?

简单的说,就是没有实现get,set,del三个方法的所有类。

让我们任意看一个函数的描述:

def call():

    pass

执行print dir(call)会得到如下结果:

  1. ['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']  

先看下dir的帮助。

dir列出给定对象的属性或者是从这个对象能够达到的对象。

回到print dir(call)方法的输出,看到,call方法,有输出的那么多个属性。其中就包含了__get__函数。但是却没有__set__和__del__函数。所以所有的类成员函数都是非数据描述符。

看一个实例数据掩盖非数据描述符的例子:

[python] view plaincopy
  1. ''''' 
  2. Created on 2013-3-29 
  3.   
  4. @author: naughty 
  5. '''  
  6.   
  7.  
    class simpleDescriptor(object):      
  8.     
     
    def __get__(self,obj,type=None) :  
  9.         
     
    return "get",self,obj,type  
  10.   
  11.  
    class D(object):  
  12.     foo=simpleDescriptor()  
  13. d=D()  
  14.  
    print d.foo  
  15. d.foo=
     
    15  
  16.  
    print d.foo  

看输出:

('get', <__main__.simpleDescriptor object at 0x02141190>, <__main__.D object at 0x025CAF50>, <class '__main__.D'>)
15

可见,实例数据掩盖了非数据描述符。

如果改成数据描述符,那么就不会被覆盖了。看下面:

[python] view plaincopy
  1. ''''' 
  2. Created on 2013-3-29 
  3.   
  4. @author: naughty 
  5. '''  
  6.   
  7.  
    class simpleDescriptor(object):      
  8.     
     
    def __get__(self,obj,type=None) :  
  9.         
     
    return "get",self,obj,type  
  10.     
     
    def __set__(self,obj,type=None) :  
  11.         
     
    pass  
  12.     
     
    def __del__(self,obj,type=None) :  
  13.         
     
    pass  
  14.   
  15.  
    class D(object):  
  16.     foo=simpleDescriptor()  
  17. d=D()  
  18.  
    print d.foo  
  19. d.foo=
     
    15  
  20.  
    print d.foo  

代码的输出如下:

  1. ('get', <__main__.simpleDescriptor object at 0x01DD1190>, <__main__.D object at 0x0257AF50>, <class '__main__.D'>)  
  2. ('get', <__main__.simpleDescriptor object at 0x01DD1190>, <__main__.D object at 0x0257AF50>, <class '__main__.D'>)  


由于是数据描述符,__set __函数体是pass,所以两次输出都是同样的内容。

最后看下__getatrr__方法。它的标准定义是:__getattr__(self,attr),其中attr是属性名

让我们来看一个简单的例子:

  1. '''  
  2. Created on 2013-3-29  
  3.   
  4. @author: naughty  
  5. '''  
  6.   
  7. class D(object):  
  8.     def __getattr__(self,attr):  
  9.         return attr  
  10.   
  11. d=D()  
  12. print d.foo,type(d.foo)  
  13. d.foo=15  
  14. print d.foo  

代码输出:

foo <type 'str'>
15

由于d对象中根本没有foo这个属性,所以python最后求助于__getattr__函数。然后打印出__getattr__函数的返回值。

注意:这里不要认为的造成无限递归

代码如下:

[python] view plaincopy
  1. ''''' 
  2. Created on 2013-3-29 
  3.   
  4. @author: naughty 
  5. '''  
  6.   
  7.  
    class D(object):  
  8.     
     
    def __getattr__(self,attr):  
  9.         
     
    return self.attr  
  10.   
  11. d=D()  
  12.  
    print d.foo  
  13. d.foo=
     
    15  
  14.  
    print d.foo  

这段代码, 会造成如下错误:

Exception RuntimeError: 'maximum recursion depth exceeded while calling a Python object'in <type 'exceptions.AttributeError'> ignored
Traceback (most recent call last):
  File "H:\final\code\PyTestService\comz\test\Copy of Copy of Test.py", line 12, in <module>
    print d.foo
  File "H:\final\code\PyTestService\comz\test\Copy of Copy of Test.py", line 9, in __getattr__
    return self.attr
  File "H:\final\code\PyTestService\comz\test\Copy of Copy of Test.py", line 9, in __getattr__
    return self.attr

....

  File "H:\final\code\PyTestService\comz\test\Copy of Copy of Test.py", line 9, in __getattr__
    return self.attr
  File "H:\final\code\PyTestService\comz\test\Copy of Copy of Test.py", line 9, in __getattr__
    return self.attr
AttributeError: 'D' object has no attribute 'attr'

造成了超过最大递归深度问题。





posted @ 2015-02-06 16:16  阳光树林  阅读(456)  评论(0编辑  收藏  举报