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一下

posted @ 2020-06-05 17:58  seas  阅读(195)  评论(0编辑  收藏  举报