zoukankan      html  css  js  c++  java
  • Python描述符 (descriptor) 详解

    1、什么是描述符?

    python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 get(), set(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。

    以上为官方定义,纯粹为了装逼使用,一般人看这些定义都有一种问候祖先的冲动!

    没关系,看完本文,你就会理解什么叫描述符了!

    2、讲解描述符前,先看一下属性:dict (每个对象均具备该属性)

    作用:字典类型,存放本对象的属性,key(键)即为属性名,value(值)即为属性的值,形式为{attr_key : attr_value}

    对象属性的访问顺序:

    ①.实例属性

    ②.类属性

    ③.父类属性

    ④.getattr()方法

    以上顺序,切记切记!

    class Test(object):
        cls_val = 1
        def __init__(self):
            self.ins_val = 10
    
            
    >>> t=Test()
    >>> Test.__dict__
    mappingproxy({'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})
    >>> t.__dict__
    {'ins_val': 10}
    
    >>> type(x)==X
    True
    
    #更改实例t的属性cls_val,只是新增了该属性,并不影响类Test的属性cls_val
    >>> t.cls_val = 20
    >>> t.__dict__
    {'ins_val': 10, 'cls_val': 20}
    >>> Test.__dict__
    mappingproxy({'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})
    
    #更改了类Test的属性cls_val的值,由于事先增加了实例t的cls_val属性,因此不会改变实例的cls_val值(井水不犯河水)
    >>> Test.cls_val = 30
    >>> t.__dict__
    {'ins_val': 10, 'cls_val': 20}
    >>> Test.__dict__
    mappingproxy({'__module__': '__main__', 'cls_val': 30, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})
    

    从以上代码可以看出,实例t的属性并不包含cls_val,cls_val是属于类Test的。

    3、魔法方法:get(), set(), delete()

    方法的原型为:

    get(self, instance, owner)

    set(self, instance, value)

    del(self, instance)

    那么以上的 self, instance owner到底指社么呢?莫急莫急,听我慢慢道来!

    首先我们先看一段代码:

    #代码 1
    
    class Desc(object):
        
        def __get__(self, instance, owner):
            print("__get__...")
            print("self : 		", self)
            print("instance : 	", instance)
            print("owner : 	", owner)
            print('='*40, "
    ")
            
        def __set__(self, instance, value):
            print('__set__...')
            print("self : 		", self)
            print("instance : 	", instance)
            print("value : 	", value)
            print('='*40, "
    ")
    
    
    class TestDesc(object):
        x = Desc()
    
    #以下为测试代码
    t = TestDesc()
    t.x
    
    #以下为输出信息:
    
    __get__...
    self :          <__main__.Desc object at 0x0000000002B0B828>
    instance :      <__main__.TestDesc object at 0x0000000002B0BA20>
    owner :      <class '__main__.TestDesc'>
    ========================================
    

    可以看到,实例化类TestDesc后,调用对象t访问其属性x,会自动调用类Desc的 __get__方法,由输出信息可以看出:

    ① self: Desc的实例对象,其实就是TestDesc的属性x

    ② instance: TestDesc的实例对象,其实就是t

    ③ owner: 即谁拥有这些东西,当然是 TestDesc这个类,它是最高统治者,其他的一些都是包含在它的内部或者由它生出来的

    到此,我可以揭开小小的谜底了,其实,Desc类就是是一个描述符(描述符是一个类哦),为啥呢?因为类Desc定义了方法 get, set.

    所以,某个类,只要是内部定义了方法 get, set, delete 中的一个或多个,就可以称为描述符(_,简单吧)

    说到这里,我们的任务还远远没有完成,还存在很多很多的疑点?

    问题1. 为什么访问 t.x的时候,会直接去调用描述符的 get() 方法呢?

    答:t为实例,访问t.x时,根据常规顺序,

    首先:访问Owner的__getattribute__()方法(其实就是 TestDesc.getattribute()),访问实例属性,发现没有,然后去访问父类TestDesc,找到了!

    其次:判断属性 x 为一个描述符,此时,它就会做一些变动了,将 TestDesc.x 转化为 TestDesc.dict[‘x’].get(None, TestDesc) 来访问

    然后:进入类Desc的 get()方法,进行相应的操作

    问题2. 从上面 代码1 我们看到了,描述符的对象 x 其实是类 TestDesc 的类属性,那么可不可以把它变成实例属性呢?

    答:我说了你不算,你说了也不算,解释器说了算,看看解释器怎么说的。

    #代码 2
    
    class Desc(object):
        def __init__(self, name):
            self.name = name
        
        def __get__(self, instance, owner):
            print("__get__...")
            print('name = ',self.name) 
            print('='*40, "
    ")
    
    class TestDesc(object):
        x = Desc('x')
        def __init__(self):
            self.y = Desc('y')
    
    #以下为测试代码
    t = TestDesc()
    t.x
    t.y
    
    #以下为输出结果:
    __get__...
    name =  x
    ========================================
    

    咦,为啥没打印 t.y 的信息呢?

    因为没有访问 get() 方法啊,哈哈,那么为啥没有访问 get() 方法呢?(问题真多)

    因为调用 t.y 时刻,首先会去调用TestDesc(即Owner)的 getattribute() 方法,该方法将 t.y 转化为TestDesc.dict[‘y’].get(t, TestDesc), 但是呢,实际上 TestDesc 并没有 y这个属性,y 是属于实例对象的,所以,只能忽略了。

    问题3. 如果 类属性的描述符对象 和 实例属性描述符的对象 同名时,咋整?

    答:还是让解释器来解释一下吧。

    #代码 3
    
    class Desc(object):
        def __init__(self, name):
            self.name = name
            print("__init__(): name = ",self.name)
            
        def __get__(self, instance, owner):
            print("__get__() ...")
            return self.name
    
        def __set__(self, instance, value):
            self.value = value
            
    class TestDesc(object):
        _x = Desc('x')
        def __init__(self, x):
            self._x = x
    
    
    #以下为测试代码
    t = TestDesc(10)
    t._x
    
    #输入结果
    __init__(): name =  x
    __get__() ...
    

    不对啊,按照惯例,t._x 会去调用 getattribute() 方法,然后找到了 实例t 的 _x 属性就结束了,为啥还去调用了描述符的 get() 方法呢?

    这就牵扯到了一个查找顺序问题:当Python解释器发现实例对象的字典中,有与描述符同名的属性时,描述符优先,会覆盖掉实例属性。

    不信?来看一下 字典 :

    >>> t.__dict__
    {}
    
    >>> TestDesc.__dict__
    mappingproxy({'__module__': '__main__', '_x': <__main__.Desc object at 0x0000000002B0BA20>, '__init__': <function TestDesc.__init__ at 0x0000000002BC59D8>, '__dict__': <attribute '__dict__' of 'TestDesc' objects>, '__weakref__': <attribute '__weakref__' of 'TestDesc' objects>, '__doc__': None})
    

    怎么样,没骗你吧?我这人老好了,从来不骗人!

    我们再将 代码3 改进一下, 删除 set() 方法试试看会发生什么情况?

     #代码 4
    
    class Desc(object):
        def __init__(self, name):
            self.name = name
            print("__init__(): name = ",self.name)
            
        def __get__(self, instance, owner):
            print("__get__() ...")
            return self.name
            
    class TestDesc(object):
        _x = Desc('x')
        def __init__(self, x):
            self._x = x
    
    
    #以下为测试代码
    t = TestDesc(10)
    t._x
    
    #以下为输出:
    __init__(): name =  x
    

    我屮艸芔茻,咋回事啊?怎么木有去 调用 get() 方法?

    其实,还是 属性 查找优先级惹的祸,只是定义一个 get() 方法,为非数据描述符,优先级低于实力属性的!!

    问题4. 什么是数据描述符,什么是非数据描述符?

    答:一个类,如果只定义了 get() 方法,而没有定义 set(), delete() 方法,则认为是非数据描述符; 反之,则成为数据描述符

    问题5. 天天提属性查询优先级,就不能总结一下吗?

    答:好的好的,客官稍等!

    getattribute(), 无条件调用

    ② 数据描述符:由 ① 触发调用 (若人为的重载了该 getattribute() 方法,可能会调职无法调用描述符)

    ③ 实例对象的字典(若与描述符对象同名,会被覆盖哦)

    ④ 类的字典

    ⑤ 非数据描述符

    ⑥ 父类的字典

    getattr() 方法

  • 相关阅读:
    Ubuntu 14 如何解压 .zip、.rar 文件
    python 自定义异常
    python 简单的自定义异常类模版
    python 获取文件目录位置
    python获取当前文件路径以及父文件路径
    python tar 打包
    python requests 上传文件
    android:ems="10"是什么意思
    django 应用中获取访问者ip地址
    curl: (6) Could not resolve host: www.baidu.com;
  • 原文地址:https://www.cnblogs.com/lllini/p/11955201.html
Copyright © 2011-2022 走看看