描述符是一种在多个属性上重复利用同一个存取逻辑的方式,他能”劫持”那些本对于self.dict的操作。描述符通常是一种包含get、set、delete三种方法中至少一种的类,给人的感觉是「把一个类的操作托付与另外一个类」。静态方法、类方法、property都是构建描述符的类。
我们先看一个简单的描述符的例子(基于我之前的分享的[Python高级编程](http://dongweiming.github.io/Expert-
Python/#43)改编,这个PPT建议大家去看看):
1 |
|
注意MyDescriptor要用新式类。调用一下:
1 |
|
这就是描述符的威力。我们熟知的staticmethod、classmethod如果你不理解,那么看一下用Python实现的效果可能会更清楚了:
1 |
|
在实际的生产项目中,描述符有什么用处呢?首先看MongoEngine中的Field的用法:
1 |
|
有非常多的Field类型,其实它们的基类就是一个描述符,我简化下,大家看看实现的原理:
1 |
|
很多项目的源代码看起来很复杂,在抽丝剥茧之后,其实原理非常简单,复杂的是业务逻辑。
接着我们再看Flask的依赖Werkzeug中的cached_property:
1 |
|
其实看类的名字就知道这是缓存属性的,看不懂没关系,用一下:
1 |
|
调用下:
1 |
|
可以看到在从第二次调用bar方法开始,其实用的是缓存的结果,并没有真的去执行。
说了这么多描述符的用法。我们写一个做字段验证的描述符:
1 |
|
我们试一试:
1 |
|
看到了吧,我们在描述符的类里面对传值进行了验证。ORM就是这么玩的!
但是上面的这个实现有个缺点,就是不太自动化,你看height =
Quantity('height')
,这得让属性和Quantity的name都叫做height,那么可不可以不用指定name呢?当然可以,不过实现的要复杂很多:
1 |
|
Quantity的name相当于类名+计时器,这个计时器每调用一次就叠加1,用此区分。有一点值得提一提,在get中的:
1 |
|
在很多地方可见,比如之前提到的MongoEngine中的BaseField。这是由于直接调用Rectangle.height这样的属性时候会报AttributeError,
因为描述符是实例上的属性。
PS:这个灵感来自《Fluent
Python》,书中还有一个我认为设计非常好的例子。就是当要验证的内容种类很多的时候,如何更好地扩展的问题。现在假设我们除了验证传入的值要大于0,还得验证不能为空和必须是数字(当然三种验证在一个方法中验证也是可以接受的,我这里就是个演示),我们先写一个abc的基类:
1 |
|
现在新加一个检查类型,新增一个继承了Validated的、包含检查的validate方法的类就可以了:
1 |
|
前面展示的描述符都是一个类,那么可不可以用函数来实现呢?也是可以的:
1 |
|
版权声明:本文由 董伟明 原创,未经作者授权禁止任何微信公众号和向掘金(juejin.im)转载,技术博客转载采用 保留署名-非商业性使用-禁止演绎 4.0-国际许可协议
python