首先介绍一下装饰器,所谓装饰器,就是接受一个函数作为参数,然后返回一个函数的函数。所谓带参数的装饰器呢?其实是返回装饰器的函数。注意啦!python的装饰器语法是一个语法糖,实际上并不强制要求你的装饰器返回函数!你的装饰器完全返回随便什么东西!int、float、bool、str等基本类型,list、tuple、map等集合类型亦或者class都可以!
倒不如说,很多很多python 的装饰器,其实返回的是class,因为class可以定义很多魔术方法,进而既可以当做函数使用,也可以当做数据使用,很多python的黑科技,都是这个原理。
而在讲描述器之前,先要说一下类的三种方法:
1)实例方法,类的实例方法大家都知道,第一个参数是调用该方法的实例。 2)类方法,用@classmethod装饰的方法,其第一个参数是调用该方法的(实例的)类。 3)静态方法,用@staticmethod装饰的方法,其可视为定义在class内部的普通函数。
就像实例方法的第一个参数是实例,以便对不同势力该方法的表现不同;类方法的第一个参数是调用其的类,因此如果被定义类方法的子类调用了类方法的话,其第一个参数将是子类,因此可以产生不同的表现。
然后,我们再说类属性的访问机制。如果从类出发访问类的属性,其实就是在类的__dict__
里查找该属性,找不到的话会以此查找继承链条MRO里后面的各个类的__dict__
。而从实例出发访问属性的时候呢?又不一样。所有对实例出发的属性的方法,都是由类的__getattribute__
方法来执行的,它会先查找实例的__dict__
,找不到的话,会查找其所属的类的__dict__
,然后是继承链条MRO里后面的各个类的__dict__
。那么为啥要用__getattribute__
?通过__getattribute__
,可以对实例的属性进行限制。比如说,如果class包含一个属性__slots__
,这是一个str的list,那么对实例定义新的属性的时候,其属性就必须包含在__slots__
里。这个确保的途径,就是__getattribute__
做的。__getattribute__
还做了一件事,那就是它查询完所有__dict__
之后如果还没有找到该属性,而class定义了__getattr__
方法的话,__getattribute__
就会调用__getattr__
方法。
很显然__getattribute__
和__getattr__
都接受两个参数,类实例self和字符串name。
说到这里,终于可以说描述器了。
描述器首先必然是一个class的实例!并且这个class必须定义了__get__
方法,以及可选的__set__
、__delete__
方法。如果定义了__set__
方法,该class称之为资料描述器,否则称之为非资料描述器。__delete__
只在删除该描述器属性时被执行。
两种描述器有啥差别?它们只在从实例访问属性时有差别。如果实例的__dict__
含有与描述器同名的属性,那么根据描述器的不同:__getattribute__
会优先返回资料描述器,其次是实例属性,最后才是非资料描述器。
写到这里,终于可以具体讲一下描述器了。
__get__
方法接受三个参数,self、instance、owner。 __set__
方法接受三个参数,self、instance、value。 __delete__
方法接受两个参数,self、instance。
注意了!假设有个类A,其类属性b是描述器类B的一个实例,而a是A的一个实例。
那么,代码“a.b”会调用B.__get__(b,a,A)
;“a.b=XXX”会调用B.__set__(b,a,XXX)
;“del(a.b)”和“delattr(a,'b')”会调用B.__delete__(b,a)
但不一定会删除class A的b属性。
而代码“A.b”则会调用B.__get__(b,None,A)
;但“A.b=XXX”不会调用B.__set__(b,None,XXX)
,而直接修改A的__dict__
;“del(A.b)”和“delattr(A,'b')”也不会调用B.__delete__(b,None)
而是直接从A的__dict__
中删除'b'键。
这样一看,大致就清楚了。描述器的实例是不会单独使用的,它总是作为类的属性(而不是实例的属性)存在。当我们从实例访问一个描述器属性的时候,其实我们是调用了描述器自己的方法,而这个方法又可以获得我们访问描述器属性的实例作为参数————因此这个方法可以伪装成实例方法!
是的,这就是关键啦。描述器的实例,明明是一个类属性,却可以伪装成实例属性!
end。
回头看看staticmethod和classmethod,就更有意思了。这俩玩意都是装饰器,但是其实它们是伪装成装饰器的类!它们的__init__
方法可以接受函数,返回一个实例,因此用这俩来装饰一个实例方法的话,实例方法实际上被替换成了一个类实例。而它们本身,却又是描述器。也就是说,实例方法变成了一个描述器实例。而这个描述器实例的__get__
方法,又会返回一个函数/方法,结果是实例方法虽然被改变成了描述器实例,但实际上却仍旧可以当成一个函数来访问!而property其实也是一个伪装成装饰器的类,同时也是描述器;而且它还有三个实例方法又可以作为装饰器使用…………
python黑科技啊!佩服佩服!!