- # 类似函数的形式
- class A:
- def __init__(self, name, score):
- self.name = name # 普通属性
- self.score = score
- def getscore(self):
- return self._score
- def setscore(self, value):
- print('setting score here')
- if isinstance(value, int):
- self._score = value
- else:
- print('please input an int')
- score = property(getscore, setscore)
- a = A('Bob',90)
- a.name # 'Bob'
- a.score # 90
- a.score = 'bob' # please input an int
分析上述调用 score 的过程
初始化时即开始访问 score, 发现有两个选项, 一个是属性, 另一个是
property(getscore, setscore)
对象, 因为后者中定义了__get__与__set__方法, 因此是一个资料描述器, 具有比属性更高的优先级, 所以这里就访问了描述器
因为初始化时是对属性进行设置, 所以自动调用了描述器的__set__方法
__set__中对 fset 属性进行检查, 这里即传入的 setscore, 不是 None, 所以调用了 fset 即 setscore 方法, 这就实现了设置属性时使用自定义函数进行检查的目的
__get__也是一样, 查询 score 时, 调用__get__方法, 触发了 getscore 方法
下面是另一种使用 property 的方法
- # 装饰器形式, 即引言中的形式
- class A:
- def __init__(self, name, score):
- self.name = name # 普通属性
- self.score = score
- @property
- def score(self):
- print('getting score here')
- return self._score
- @score.setter
- def score(self, value):
- print('setting score here')
- if isinstance(value, int):
- self._score = value
- else:
- print('please input an int')
- a = A('Bob',90)
- # a.name # 'Bob'
- # a.score # 90
- # a.score = 'bob' # please input an int
下面进行分析
在第一种使用方法中, 是将函数作为传入 property 中, 所以可以想到是否可以用装饰器来封装
get 部分很简单, 访问 score 时, 加上装饰器变成访问 property(score) 这个描述器, 这个 score 也作为 fget 参数传入__get__中指定调用时的操作
而 set 部分就不行了, 于是有了 setter 等方法的定义
使用了 property 和 setter 装饰器的两个方法的命名都还是 score, 一般同名的方法后面的会覆盖前面的, 所以调用时调用的是后面的 setter 装饰器处理过的 score, 是以如果两个装饰器定义的位置调换, 将无法进行属性赋值操作.
而调用 setter 装饰器的 score 时, 面临一个问题, 装饰器 score.setter 是什么呢? 是 score 的 setter 方法, 而 score 是什么呢, 不是下面定义的这个 score, 因为那个 score 只相当于参数传入. 自动向其他位置寻找有没有现成的 score, 发现了一个, 是 property 修饰过的 score, 这是个描述器, 根据 property 的定义, 里面确实有一个 setter 方法, 返回的是 property 类传入 fset 后的结果, 还是一个描述器, 这个描述器传入了 fget 和 fset, 这就是最新的 score 了, 以后实例只要调用或修改 score, 使用的都是这个描述器
如果还有 del 则装饰器中的 score 找到的是 setter 处理过的 score, 最新的 score 就会是三个函数都传入的 score
对最新的 score 的调用及赋值删除都跟前面一样了
property 的原理就讲到这里, 从它的定义我们可以知道它其实就是将我们设置的检查等函数传入 get set 等方法中, 让我们可以自由对属性进行操作. 它是一个框架, 让我们可以方便传入其他操作, 当很多对象都要进行相同操作的话, 重复就是难免的. 如果想要避免重复, 只有自己写一个类似 property 的框架, 这个框架不是传入我们希望的操作了, 而是就把这些操作放在框架里面, 这个框架因为只能实现一种操作而不具有普适性, 但是却能大大减少当前问题代码重复问题
下面使用描述器定义了 Checkint 类之后, 会发现 A 类简洁了非常多
- class Checkint:
- def __init__(self, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- else:
- return instance.__dict__[self.name]
- def __set__(self, instance, value):
- if isinstance(value, int):
- instance.__dict__[self.name] = value
- else:
- print('please input an integer')
- # 类似函数的形式
- class A:
- score = Checkint('score')
- age = Checkint('age')
- def __init__(self, name, score, age):
- self.name = name # 普通属性
- self.score = score
- self.age = age
- a = A('Bob', 90, 30)
- a.name # 'Bob'
- a.score # 90
- # a.score = 'bob' # please input an int
- # a.age='a' # please input an integer
描述器的应用
因为我本人也刚刚学描述器不久, 对它的应用还不是非常了解, 下面只列举我现在能想到的它有什么用, 以后如果想到其他的再补充
首先是上文提到的, 它是实例方法, 静态方法, 类方法, property 的实现原理
当访问属性, 赋值属性, 删除属性, 出现冗余操作, 或者苦思无法找到答案时, 可以求助于描述器
具体使用 1: 缓存. 比如调用一个类的方法要计算比较长的时间, 这个结果还会被其他方法反复使用, 我们不想每次使用和这个相关的函数都要把这个方法重新运行一遍, 于是可以设计出第一次计算后将结果缓存下来, 以后调用都使用存下来的结果. 只要使用描述器在__get__方法中, 在判断语句下,
obj.__dict__[self.name] = value
. 这样每次再调用这个方法都会从这个字典中取得值, 而不是重新运行这个方法.(例子来源 https://www.jianshu.com/p/250f0d305c35 最后的那个例子)
参考资料
参考网页如下
官网的中文翻译, 给出了描述器功能的整体框架及一些实例
官网英文 https://docs.python.org/3.5/howto/descriptor.html
简书文章 https://www.jianshu.com/p/250f0d305c35 , 主要讲解访问描述器顺序, 静态方法, 类方法和实例方法下的访问情况
简书文章 https://www.jianshu.com/p/58f1df955bda , 对官网的 @Property 细节解读
一篇译文 http://python.jobbole.com/81899/ 可以再看看他下面附的参考资料 (不要先看这篇, 这篇有点深, 而且个人认为他有些实现方法舍近求远)
如果想看更多文章, 搜索时注意: 搜索 "描述器" 得到的文章高度重复, 基本上就是上面几篇了, 搜 "描述符" 会找到更多文章
来源: http://www.bubuko.com/infodetail-3076296.html