You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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方法。

简单说:

  1. lazyproperty返回的lazy被@property装饰后,成为了属性描述符,会关联到Circle类的area属性上。
  2. 当你创建实例c = Circle(5)并访问c.area时,Python会自动调用lazy方法,同时把c这个实例作为self参数传进去——这和普通类方法的参数传递逻辑完全一致,只是@property让调用方式变成了属性访问(不用加括号)。

三、完整运行流程(以c = Circle(5)为例)

第一次访问c.area:

  1. 触发lazy(self)调用,self就是c,打印实例地址<__main__.Circle object at 0x...>。
  2. 检查c是否有_lazy_area属性(这是lazyproperty用原函数名生成的缓存键),第一次访问时不存在。
  3. 调用func(self)——这里的func就是原本的area函数,把self=c传进去,执行打印和面积计算。
  4. 用setattr(self, name, value)把计算结果存入c._lazy_area,作为缓存。
  5. 返回计算结果。

第二次及以后访问c.area:

  1. 触发lazy(self)调用,self仍然是c。
  2. 检查发现c已经有_lazy_area属性,直接返回缓存值,不再执行原area的计算逻辑,实现了懒加载+缓存的效果。

四、@property的作用

lazy被@property装饰后,把方法伪装成了只读属性:

  • 访问时不用加括号(c.area而非c.area()),用法和普通实例属性一致,更符合直觉。
  • 内部可以封装复杂计算逻辑,但对外暴露简洁的属性接口。

内容的提问来源于stack exchange,提问作者user2994219

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 13:57:33