zoukankan      html  css  js  c++  java
  • Python学习笔记——描述符

    在Python中,访问一个属性的优先级顺序按照如下顺序:

    1. 类属性
    2. 数据描述符
    3. 实例属性
    4. 非数据描述符
    5. __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__": <attribute "__dict__" of "A" objects>, "__module__": "__main__",
     "foo": 1.3, "__weakref__":  <attribute "__weakref__" of "A" objects>, "__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__":  <attribute "__dict__" of "A" objects >, "__module__": "__main__", 
    "foo":  <__main__.simpleDescriptor object at 0x00C46930 >, 
    "__weakref__":  <attribute "__weakref__" of "A" objects >, 
    "__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",  <__main__.simpleDescriptor object at 0x00C46870 >, 
     <__main__.D object at 0x00C46890 >,  <class "__main__.D" >)
    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 <type "str" >
       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
    
  • 相关阅读:
    Android中Context具体解释 ---- 你所不知道的Context
    JDK6、Oracle11g、Weblogic10 For Linux64Bit安装部署说明
    matplotlib 可视化 —— 定制 matplotlib
    matplotlib 可视化 —— 移动坐标轴(中心位置)
    matplotlib 可视化 —— 移动坐标轴(中心位置)
    matplotlib 可视化 —— 定制画布风格 Customizing plots with style sheets(plt.style)
    matplotlib 可视化 —— 定制画布风格 Customizing plots with style sheets(plt.style)
    指数函数的研究
    指数函数的研究
    指数分布的研究
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2093916.html
Copyright © 2011-2022 走看看