前言描述符描述符协议示例资料描述符与非资料描述符描述符的陷阱第一点第二点感谢
前言
同样的,因为工作需要而接触Django。恰巧组里的测试要求项目要对用户所有输入字段做校验,为让代码整洁优雅,我选择了Django自带的表单验证器。但我在这里并非要说Django,而是表单验证器实现的原理。
由于这里边涉及到一些其他知识点,所以极建议到CSDN上阅读此篇,那样体验更佳。
但上述并非是我迫切渴望说出来的。其实想说天骤冷了,望君加衣。
描述符
在Python中,描述符作为一个用语言描述起来会有些抽象的概念。其定义有如下说法:
一般来说,描述符是一个具有绑定行为的对象属性,其属性的访问被描述符协议方法覆写。这些方法是__get__()、__set__()和__delete__(),一个对象中只要包含了这三个方法(译者注:包含至少一个),就称它为描述符。
而我认为,如果可以通俗地去解释“为什么需要描述符”,会让初学者更易接纳。
如果你知道Python中的property——也可能不曾听过,但你可以看我的《@property的使用》——就会晓得我们可以对实例属性操作时做一些限制,比方说陌生人的姓名必须得字符串类型(这里当然会有些不严谨,因为Python中如也是字符串类型),如果不是,就抛异常TypeError。代码可以如下:
运行结果如下:
乍一看,上述代码似乎满足了我们的需求。其实不然,存在两个缺点。
其一,当我们初始化一个对象,就给name传int类型的参数,程序会默然允许:
其二,当我们需要限制的属性较多时:
为了一一限制,便不得不对应的去定义@name.setter,@age.setter…… 代码怎么不优雅了?
Python的代码需要优雅,这就是引进描述符的原因。你也可以认为描述符是property的升级版。
描述符协议
描述符协议包含:
用于访问属性的值。当请求的属性不存在时,抛出
设置操作
删除操作
一个对象中只要包含了这三个方法中的一个,就称它为描述符。
示例
此时
或者
资料描述符与非资料描述符
认为,如果一个描述符只定义了方法,则为非资料描述符;如果同时定义了和,为资料描述符:
资料描述符:当类属性与实例属性同名时,优先访问类属性
非资料描述符:当类属性与实例属性同名时,优先访问实例属性
描述符的陷阱
第一点:描述符必须在类的层次上
这是因为:只有类层次上的描述符才会默认调用__get__。
第二点:确保实例属性属于实例本身
事实上类属性是该类实例对象共有的,可参看我的《类属性与实例属性》。所以很可能出现“一荣俱荣,一损俱损”的现象:
解决方案:
在描述符类中维护一个字典,将每个实例对象作为字典的key,而类属性对应的值作为字典的value:
这样做的缺点是,也许会碰到不可以被hash的实例,那么就不能作为字典的key。常用的解决方案是:为描述符增加标签,并通过这个标签,把本来应该访问类属性这一过程,“偷偷地”转化成访问实例属性的过程:
这种做的缺点是,你可能会在没有察觉的情况下修改了name值:
建议标签名最好与描述符对象的名字相同。
最后可以利用元类来简化这个过程:
如果你对元类不熟悉,不妨参看我的《元类》。
感谢
参考Python描述符简介
参考【译】Python描述符指南
参考解密 Python 的描述符(descriptor)
领取专属 10元无门槛券
私享最新 技术干货