python描述器

原创
2017/01/04 23:34
阅读数 265

首先介绍一下装饰器,所谓装饰器,就是接受一个函数作为参数,然后返回一个函数的函数。所谓带参数的装饰器呢?其实是返回装饰器的函数。注意啦!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黑科技啊!佩服佩服!!

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
1 收藏
0
分享
返回顶部
顶部