• 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
    
  • 相关阅读:
    启动控制面板命令大全
    C#下载网页为mht文件
    基于C#语言的可编程表达式计算器设计
    FileSystemWatcher监视文件变动
    C#梁朝伟变刘德华之山寨实现
    Json之语法,格式
    Regex类
    C# 索引器
    优化正则表达式的诀窍
    正则表达式(二) 零宽断言与懒惰匹配以及平衡组
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2093916.html
Copyright © 2020-2023  润新知