Python惰性属性装饰器原理解惑:self为何指向Circle对象
详解懒加载装饰器
lazyproperty的运行机制 先看完整代码:
def lazyproperty(func): name = '_lazy_' + func.__name__ @property def lazy(self): print(self) if hasattr(self, name): return getattr(self, name) else: value = func(self) setattr(self, name, value) return value return lazy import math class Circle: def __init__(self, radius): self.radius = radius @lazyproperty def area(self): print('Computing area') return math.pi * self.radius ** 2
一、装饰器的本质:替换被装饰的对象
Python里的@xxx装饰器是语法糖,核心逻辑是:把被装饰的函数(比如这里的area)传给装饰器函数(lazyproperty),再把装饰器返回的新对象赋值回原来的函数名(area)。
所以@lazyproperty装饰Circle.area的过程,等价于手动执行:
# 先定义原本的area函数 def area(self): print('Computing area') return math.pi * self.radius ** 2 # 将area传入lazyproperty,把返回的lazy方法赋值给area area = lazyproperty(area)
二、lazy函数里的self为什么是Circle实例?
核心逻辑是:被@property装饰的lazy方法,最终会绑定为Circle类的属性访问器,当通过Circle实例访问该属性时,Python会自动把当前实例作为第一个参数传入lazy方法。
简单说:
lazyproperty返回的lazy被@property装饰后,成为了属性描述符,会关联到Circle类的area属性上。- 当你创建实例
c = Circle(5)并访问c.area时,Python会自动调用lazy方法,同时把c这个实例作为self参数传进去——这和普通类方法的参数传递逻辑完全一致,只是@property让调用方式变成了属性访问(不用加括号)。
三、完整运行流程(以c = Circle(5)为例)
第一次访问c.area:
- 触发
lazy(self)调用,self就是c,打印实例地址<__main__.Circle object at 0x...>。 - 检查
c是否有_lazy_area属性(这是lazyproperty用原函数名生成的缓存键),第一次访问时不存在。 - 调用
func(self)——这里的func就是原本的area函数,把self=c传进去,执行打印和面积计算。 - 用
setattr(self, name, value)把计算结果存入c._lazy_area,作为缓存。 - 返回计算结果。
第二次及以后访问c.area:
- 触发
lazy(self)调用,self仍然是c。 - 检查发现
c已经有_lazy_area属性,直接返回缓存值,不再执行原area的计算逻辑,实现了懒加载+缓存的效果。
四、@property的作用
lazy被@property装饰后,把方法伪装成了只读属性:
- 访问时不用加括号(
c.area而非c.area()),用法和普通实例属性一致,更符合直觉。 - 内部可以封装复杂计算逻辑,但对外暴露简洁的属性接口。
内容的提问来源于stack exchange,提问作者user2994219
相关产品推荐
相关产品推荐

