(转)面向对象(深刻)|python描述器详解

原文:https://zhuanlan.zhihu.com/p/32764345html

 

# 相似函数的形式 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的过程python

  • 初始化时即开始访问score,发现有两个选项,一个是属性,另外一个是property(getscore, setscore)对象,由于后者中定义了__get____set__方法,所以是一个资料描述器,具备比属性更高的优先级,因此这里就访问了描述器
  • 由于初始化时是对属性进行设置,因此自动调用了描述器的__set__方法
  • __set__中对fset属性进行检查,这里即传入的setscore,不是None,因此调用了fsetsetscore方法,这就实现了设置属性时使用自定义函数进行检查的目的
  • __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等方法的定义
  • 使用了propertysetter装饰器的两个方法的命名都仍是score,通常同名的方法后面的会覆盖前面的,因此调用时调用的是后面的setter装饰器处理过的score,是以若是两个装饰器定义的位置调换,将没法进行属性赋值操做。
  • 而调用setter装饰器的score时,面临一个问题,装饰器score.setter是什么呢?是scoresetter方法,而score是什么呢,不是下面定义的这个score,由于那个score只至关于参数传入。自动向其余位置寻找有没有现成的score,发现了一个,是property修饰过的score,这是个描述器,根据property的定义,里面确实有一个setter方法,返回的是property类传入fset后的结果,仍是一个描述器,这个描述器传入了fgetfset,这就是最新的score了,之后实例只要调用或修改score,使用的都是这个描述器
  • 若是还有del则装饰器中的score找到的是setter处理过的score,最新的score就会是三个函数都传入的score
  • 对最新的score的调用及赋值删除都跟前面同样了

property的原理就讲到这里,从它的定义咱们能够知道它其实就是将咱们设置的检查等函数传入get set等方法中,让咱们能够自由对属性进行操做。它是一个框架,让咱们能够方便传入其余操做,当不少对象都要进行相同操做的话,重复就是不免的。若是想要避免重复,只有本身写一个相似property的框架,这个框架不是传入咱们但愿的操做了,而是就把这些操做放在框架里面,这个框架由于只能实现一种操做而不具备普适性,可是却能大大减小当前问题代码重复问题ide

下面使用描述器定义了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

描述器的应用

由于我本人也刚刚学描述器不久,对它的应用还不是很是了解,下面只列举我如今能想到的它有什么用,之后若是想到其余的再补充ui

  • 首先是上文提到的,它是实例方法、静态方法、类方法、property的实现原理
  • 当访问属性、赋值属性、删除属性,出现冗余操做,或者苦思没法找到答案时,能够求助于描述器
  • 具体使用1:缓存。好比调用一个类的方法要计算比较长的时间,这个结果还会被其余方法反复使用,咱们不想每次使用和这个相关的函数都要把这个方法从新运行一遍,因而能够设计出第一次计算后将结果缓存下来,之后调用都使用存下来的结果。只要使用描述器在__get__方法中,在判断语句下,obj.__dict__[self.name] = value。这样每次再调用这个方法都会从这个字典中取得值,而不是从新运行这个方法。(例子来源最后的那个例子)

参考资料

参考网页以下spa

  • 官网的中文翻译,给出了描述器功能的总体框架及一些实例
  • 官网英文
  • 简书文章,主要讲解访问描述器顺序,静态方法、类方法和实例方法下的访问状况
  • 简书文章,对官网的@Property细节解读
  • 一篇译文能够再看看他下面附的参考资料(不要先看这篇,这篇有点深,并且我的认为他有些实现方法舍近求远)
  • 若是想看更多文章,搜索时注意:搜索“描述器”获得的文章高度重复,基本上就是上面几篇了,搜“描述符”会找到更多文章
相关文章
相关标签/搜索