# 类似函数的形式
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​​。这样每次再调用这个方法都会从这个字典中取得值,而不是重新运行这个方法。(​​例子来源​​最后的那个例子)