一、回顾python内置的装饰器@property
@property的作用就是将类的函数属性同对象的数据属性一样可供对象直接调用(静态属性),而不需要加()
1 class Room:
2 def __init__(self,name,width,length):
3 self.name = name
4 self.width = width
5 self.length = length
6
7 @property #这个装饰器可以使得Room实例化的对象直接调用area这个函数属性
8 def area(self):
9 return '%s 的面积是 %d'%(self.name,self.length * self.width)
10 r1 = Room('卧室',10,5)
11 print(r1.area) #'卧室 的面积是 50'
12 print(Room.area) #类调用静态属性 <property object at 0x000001BEBAF218B8>
二、我们可以通过描述符和类的装饰器来自己制作类似于上述的property
装饰器也可以是类,描述符主要用到__get__方法,返回的就是需要调用函数的返回值
1 class diy_property:
2 def __init__(self,func):
3 self.func = func
4 def __get__(self, instance, owner): #存在__get__所以这个diy_property类就是一个描述符
5 '''类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身'''
6 if instance == None:
7 return self #仿照property,如果通过类调用静态属性,则返回装饰器的实例对象
8 res = self.func(instance) #===> res = r2.area(被修饰类的self) ===> res = r2.area(r2)
9 return res
10
11 class Room:
12 def __init__(self,name,width,length):
13 self.name = name
14 self.width = width
15 self.length = length
16
17 @diy_property #==> area=diy_property(area) 相当于定义了一个类属性,即描述符
18 def area(self):
19 return '%s 的面积是 %d' % (self.name, self.length * self.width)
20
21 r2 = Room('客厅',15,8)
22 print(r2.area) #客厅 的面积是 120
23 print(Room.area) #类调用静态属性 <__main__.diy_property object at 0x000001BEBB0AD1D0>
24 # 通过类的装饰器和描述符基本完成了@property的功能
三、通过自定制property实现延迟计算
什么是延迟计算:类属性的延迟计算就是将类的属性定义成一个property,只在访问的时候才会计算,而且一旦被访问后,结果将会被缓存起来,不用每次都计算。
所以解决思路就是:将计算的结果存放至实例的属性字典中,这样再次访问这个静态属性的时候,会直接从实例的属性字典中拿取。避免了重复计算。
1 class Lazy_property:
2 def __init__(self,func):
3 self.func = func
4 def __get__(self, instance, owner):
5 print('执行我了~~~')
6 if instance == None: #如果是类本身在调用静态属性时
7 return self #返回装饰器实例对象
8 else:
9 res = self.func(instance)
10 setattr(instance,self.func.__name__,res) #将静态属性的值放入实例的属性字典中,key是静态属性的函数名,value是静态属性的值
11 return res
12
13 class Room:
14 def __init__(self,name,width,length):
15 self.name = name
16 self.width = width
17 self.length = length
18
19 @Lazy_property
20 def area(self):
21 return '%s的面积是%d'%(self.name,self.width*self.length)
22 r3 = Room('厨房',4,7)
23 print(r3.area)
24 print(r3.__dict__)
25 #>>>执行我了~~~
26 # >>>厨房的面积是28
27 # >>>{'name': '厨房', 'width': 4, 'length': 7, 'area': '厨房的面积是28'}
28
29 #我们再次调用area这个静态属性
30 print(r3.area)
31 # >>>'厨房的面积是28'
可以发现,再次调用area这个静态属性,程序不会再去调用装饰器描述符中的__get__方法了,因为我们定义的描述符没有__set__方法,所以是一个非数据描述符。所以它的优先级低于实例属性,因为第一次调用area静态属性的时候,描述符就将该属性设置到实例的属性字典中,所以下一次调用
的时候会优先从实例属性字典中查找。