由property的形式可以看出,@property 也是在调用一个装饰器,所以,装饰器不光可以是一个函数,也可以是一个类
#默认的property
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
@property
def cal_area(self):
return self.width * self.length
r1 = Room("卧室", 20, 20)
print(r1.cal_area)
自定制实现property
class Lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance == None: #注意到类调用被property修饰的属性时会返回property object,所以判断instance是否为None(类调用是instance为None)来判断是否返回Lazyproperty的实例自己
return self
print("运行啦")
return self.func(instance)#注意这里必须要有instance参数,因为不是实例调用不会自动传参
# return instance.width * instance.length # 这种写法相当于再写一遍
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
@Lazyproperty # 本质上是area = Lazyproperty(area) 这也是Lazyproperty为什么必须有__init__函数的原因(其实本质上还是一个描述符)
def cal_area(self):
return self.width * self.length
r1 = Room("卧室", 20, 20)
print(r1.cal_area)
类的描述符实现延时计算
需求:需要计算的内容只计算一次,之后都直接调用
class Lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance == None:
return self
print("运行啦")
res = self.func(instance)
setattr(instance, "cal_area", res)
return res
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
@Lazyproperty
def cal_area(self):
return self.width * self.length
r1 = Room("卧室", 20, 20)
print(r1.cal_area)
print(r1.__dict__)
这就实现了只有第一次调用时实例会在类中找到描述符(实例属性>非数据描述符),得出计算结果之后将结果加入实例的属性字典中,之后的每一次调用都直接调用属性字典中的内容。
注意,当在描述符中加入__set__(self, instance, value)方法后,延时计算的功能就消失了,因为数据描述符>实例属性,所以每次调用都会到描述符中执行__get__()方法
property的其他内容
被property修饰的函数不能像普通函数一样修改和删除,需要为该函数的修改和删除定义专门的函数
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
@property
def cal_area(self):
return self.width * self.length
r1 = Room("卧室", 20, 20)
r1.cal_area = "dsa" #会报错AttributeError: can't set attribute
del r1.cal_area #会报错AttributeError: can't delete attribute
定义专门的修改和删除函数
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
@property
def cal_area(self):
print("get运行")
return self.width * self.length
@cal_area.setter
def cal_area(self, val):
print("set运行")
pass
@cal_area.deleter
def cal_area(self):
print("del运行")
pass
r1 = Room("卧室", 20, 20)
print(r1.cal_area)
r1.cal_area = "dsa"
或者采用以下方式
def get_cal_area(self):
print("get运行")
return self.width * self.length
def set_cal_area(self, val):
print("set运行")
def del_cal_area(self):
print("del运行")
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
cal_area = property(get_cal_area, set_cal_area, del_cal_area)
r1 = Room("卧室", 20, 20)
print(r1.cal_area)
r1.cal_area = "dsa"
用法例子
注意这里调用和set函数不要对同一个值进行修改,会导致虽然实例的属性字典中加入了新内容,但是调用该属性时依旧去描述符代理的get函数中得出属性结果
class Good:
def __init__(self):
self.original_price = 80
self.discount = 0.8
@property
def price(self):
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, val):
self.original_price = val
@price.deleter
def price(self):
del self.original_price
obj = Good()
print(obj.price)
obj.price = 200
print(obj.price)
del obj.price
描述符总结
描述符可以实现大部分python类特性中的底层结构,包括@calssmethod @staticmethod @property和__slot__属性等
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件