首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

一切皆对象——Python面向对象:描述符(中)

本篇文章继续解释描述符的原理和应用。

接着上篇文章的例子来看:

本例中,描述符定义时传入了被描述的属性名称,如。类在构建时,描述符是先于被执行的(因为是类属性)。之后在执行方法进行初始化时,描述符就开始起作用了,就开始调用进行赋值了:

当通过实例访问属性时,描述符的方法被调用,方法中将(也就是类的实例)的属性(也就是实例化描述符时传进来的属性名)返回。这里为什么要使用的方式返回属性而不使用点运算符呢?其一是因为属性名称是一个变量,所以需要通过特殊属性方式返回;更重要的原因是,使用点运算符就好像在中发生的事情一样,又一次调用了,之后又遇到了点运算符,又一次调用……最终,递归深度超出了Python最高限制,就会抛出异常,为属性赋值也是类似的道理:

另外一点在于,实例与类都定义了同名的属性。按照我们之前的看到例子来看,实例属性应当会优先于类的属性被返回:

而具有描述符的属性则会先调用描述符的方法,这说明点运算符操作针对描述符有一套特殊的处理方式,这一点我们在后续介绍。

何property是描述符

也可以充当实例到属性之间的桥梁,所不同的通常将类内的同名方法作为描述符的等特殊方法:

我们看到,正是作为类的属性而定义的。接收的三个参数(共需要4个参数,第四个是函数文档,这里忽略掉了)则分别对应着描述符的三个方法,我们可以利用普通的描述符写法来自己实现一个的功能,只需要在调用特殊方法时转而调用参数提供的方法即可:

的装饰器形式只是增加了一个语法糖,改变了接收三个参数的方式,其本质并没有变化,我们也可以为我们的增加装饰器功能:

这其中的机制是怎样的呢?我们一点点来看。首先我们知道装饰器语法糖其原理是给函数包装一层再返回,所以:

等价于:

相当于实例化了一个类,第一个参数(即)是函数,并返回了一个同名实例。经过第一个装饰器后,成为了一个实例,它只有一个属性,另外两个属性均为。之后,开始定义和。同样的道理:

等价于

等号右侧第一个是上面创建的实例,调用的是中定义的方法:

是实例本身,那么则返回的是类,而后面的语句相当于又实例化了一个新的实例并返回,所不同的是,这里的方法是传入的参数,而传入的参数正是上面等号右边第二个,也就是作用的方法。另外两个方法保持本身方法不变。这样,经过这个装饰器后,就拥有了和两个方法了。是相同的过程。作为描述符,自然需要,和三个方法,因为我们目的是在托管类内定义描述符的方法,所以这三个方法的内容就是直接调用,和即可。这样,一个同原生功能类似的描述符就创建完成了。

我们再给出一个缓存的栗子,来加深对描述符的认识。假设我们有一个类,需要频繁做矩阵求逆(这里求逆矩阵我们利用实现)。而这个类中的矩阵可能改变,也可能不变。我们尝试将矩阵求逆的结果缓存上,当矩阵没有变化时,直接返回缓存的结果

在类中定义的和重载了和两个运算符,便于矩阵比较。在描述符类中,我们通过判断矩阵是否变化了来决定是否更新缓存缓存被存入了实例的中,由于采用更改了名字,所以描述符的访问不会被覆盖。结果我们看到,在第一次访问属性时,耗时约2.2556秒,第二次访问因为有了缓存,只用了0.006秒,相当于只读取了一个结果。第三次之前我们把矩阵改变了,结果自然需要重新计算逆矩阵,又耗时2.15067秒。有人可能会问,我为何不在类内部去实现这套缓存逻辑?原因其一在于利用描述符可以更好地分解类的功能,其二在于可以复用于任意的一元操作

·END·

它不只是Python

人生苦短 大道至简

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180720G1G1BT00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券