zoukankan      html  css  js  c++  java
  • python描述器

    写这一篇完全不在我的计划中,机缘巧合下碰到的

    这两天在看property的实现原理,其中牵扯到了一个名词--描述器,这个概念说实话我还是挺模糊的 ,结果一查才知道,描述器其实应用很广泛,只不过平常使用中它都属于有点底层的实现,所以没怎么接触过,既然碰上了,正好就好好来捋一捋

    什么是描述器?描述器也称为描述符,是实现了__get__(), __set__(), 和 __delete__() 三个方法的对象,这三个方法称为描述符协议,实现了这三个方法任意一个的类称为描述符,描述符的作用是用来代理一个类的属性;注意了,这里面涉及了两个类,别弄混了,第一个类实现了三个方法其中任意一个或几个,它叫做描述符;第二个类描述符的作用是用来代理一个类的属性这句话里面的 一个类,是被描述符所作用的类,注意了,用来代理的一个类有两重含义,一层是这个描述符代理了这个类,第二层是描述符只有在类中使用才可以被称为描述符。这一点一定要弄清楚。

    这里代理的意思我强调一下,A代理了B的属性,那么A就接管了B的属性,也就是通过A完全可以操作B的属性
    

    需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例,我们通过查看实例和类的字典即可知晓,也就是在上面说的第二个类中,描述符只能被定义成类属性。

    描述符分为数据描述符(data descriptor)和非数据描述符(non-data descriptor),如果一个类实现了__set__(),则称为数据描述符,否则就是非数据描述符。为什么要区分这个,因为涉及到了python中查找的优先级:数据描述符 > 实例属性 > 非数据描述符 > 类属性 > 找不到的属性触发__getattr__()

    一味的说概念其实很不好理解,我们下面来看看描述符起了什么作用

    class De:
        def __init__(self, a=1):
            self.a = a
    
        def __get__(self, instance, owner):        // 实现__get__()协议,所以De这个类是一个描述符
            return self.a
    
    
    class PythonSite:
        a = 2
        de = De()                                  // 声明为类属性
        
        def __init__(self):
            self.a = "hello!"
    p = PythonSite()
    p.de                                           //这里会输出什么?答案是1  为什么?当调用de的时候,de判定为描述符对象,所以自动的调用了__get__()方法,return了self.a
    
    下面我们来回到上面的:
    数据描述符 > 实例属性 > 非数据描述符 > 类属性 > 找不到的属性触发`__getattr__()`
    
    ## 要讲这个东西,这里要插上一嘴,先看下面的示例
    
    class B:
        a = 1
        
        def __init__(self):
            self.a = 123
    b = B()
    b.a                       //这里输出是多少?   答案是123  为什么?当实例对象去访问类空间的时候,比如找a这个变量,第一次查询是从b.__dict__里面去找(也就是实例的属性集里面去找,注意了 这里我可没有说方法集),如果在b.__dict__查询不到,就会去type(b).__dict__查询(也就是类的方法和属性集里面去找,还找不到就getattr),最后都找不到就会报错,所以实例对象也可以执行类对象的所有属性和方法,但是属性的优先级实例属性是大于类属性的。**但是方法可不是这样**,这一点一定要注意,b.__dict__里面是没有方法的,只有属性,方法全是在type(b).__dict__里面,如果类和实例方法重名,是会后面的覆盖前面的!这是个坑别踩进去了。
    同理可得,类是不能执行实例属性的,因为根本就找不到,类直接会在type(b).__dict__里面找然后getattr,找不到就报错。
    但是要注意的还是**方法**,原理是完全不一样的!所谓类不能执行实例方法并不是找不到,而是找到了,但是参数不对,因为实例方法的第一个参数是self,这个self可以看成一个语法糖,就代表的实例本身,是为了绑定实例使用的,所以如果类调用实例方法,第一个参数传进去一个实例对象,比如下面的写法,一点问题没有
    class B:
        a = 1
        
        def test_method(self):
            self.a = 123
            
    B.test_method(B())  // 完全可以执行,就是这么XX
    

    然后让我们再次回到:

    数据描述符 > 实例属性 > 非数据描述符 > 类属性 > 找不到的属性触发__getattr__()

    这时就很清晰了,传统的属性查找顺序是:实例属性 > 类属性 >找不到的属性触发__getattr__(),描述符干涉了这个过程,插入了数据描述符和非数据描述符。也就是有数据描述符,先去访问数据描述符,show code:

    class De:
        def __init__(self, a=121):
            self.a = a
            pass
    
        def __get__(self, instance, owner):
            print("in get")
            return self.a
    
        def __set__(self, instance, value):
            print("in set")
            self.a = 123131231232
            
    class B:
        a = De()
    
        def __init__(self):
            self.a = 123
    
    b = B()
    print(b.a)                            // 结果是123131231232,当有数据描述符,并且和实例属性重名的时候,先执行__set__,然后执行__get__,这个看一下打印就很清晰了,而接下来我们在看看__dict__成了什么
    print(b.__dict__)
    print(type(b).__dict__)
    # 下面是上面两个print的打印
    {}        // 可以看到,a属性没有了!而下面type(b).__dict__多出来了一个a为key的De object,更神奇的是,它的位置看到没有,甚至在__init__之前
    {'__module__': '__main__', 'a': <__main__.De object at 0x7f67649dad68>, '__init__': <function B.__init__ at 0x7f67649dd2f0>, 'test_1method': <function B.test_1method at 0x7f67649dd378>, 'test_method': <classmethod object at 0x7f67649dadd8>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
    

    这个实现原理我会后面补充,这次就先写到这里,暂时tag一下

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 有任何问题请随时交流~ Email: araise1@163.com
  • 相关阅读:
    leetcode46 Permutations
    leetcode75 Sort Colors
    leetcode347 Top K Frequent Elements
    目录文件的浏览、管理及维护(二).作业
    目录文件的浏览、管理及维护(一).作业
    Linux系统基础.作业
    补码原码反码
    第一次测试感想
    总结八
    假期总结七
  • 原文地址:https://www.cnblogs.com/seasen/p/13051177.html
Copyright © 2011-2022 走看看