Python函数如何充当描述符?结合typed_property示例解析类属性赋值时prop的调用原因
核心结论:执行
name = typed_property('name', str)这行类内赋值语句时,根本不会触发prop()的内部逻辑 你之所以会觉得这个阶段会调用prop,本质是混淆了「构造描述符对象的过程」和「触发描述符协议的过程」这两个完全独立的执行阶段。
两个阶段的流程拆解
阶段1:Person类被解释器逐行解析的类定义阶段
这就是你说的执行name = typed_property('name', str)的场景,整个流程是:
- 解释器调用
typed_property工厂函数,传入name='name'、expected_type=str两个参数 - 函数内部依次完成操作:
- 计算存储用的实例属性名
storage_name = '_name' - 用
@property装饰器把普通函数prop包装成Python内置property类的实例——这个动作只是把prop从普通函数变成了持有getter逻辑的描述符对象,根本没有运行prop内部的代码 - 用
@prop.setter装饰器给这个描述符对象绑定带类型校验的setter逻辑
- 计算存储用的实例属性名
- 最后把这个已经构造完成的property描述符对象,作为typed_property的返回值,赋值给Person类的类属性
name
这个阶段全程没有任何prop内部逻辑的执行:你拿到的prop只是一个存了getter、setter函数引用的普通对象,挂到类上当类属性而已,和其他类属性没有区别。age属性的构造流程和它完全一致。
阶段2:对Person类的实例做属性读/写操作的描述符触发阶段
只有走到这一步,你写在prop里的代码才会被真正调用:
- 当你给实例属性赋值(比如执行
p = Person("Li Lei", 18),走到__init__里的self.name = name这行)时,解释器会在Person的类属性字典里查找name对应的对象,发现它是实现了__set__方法的数据描述符,就会自动调用这个描述符的__set__方法——也就是你写的那个带类型校验的setter逻辑 - 当你读取实例属性(比如执行
print(p.age))时,解释器发现age是带__get__方法的描述符,就会自动调用对应的getter逻辑,返回实例上存储的_age属性值
为什么会产生“类内赋值时调用prop”的错觉?
核心是没理清property装饰器的运行机制:
- 你最初写的那个getter版本的
prop函数,在经过@property装饰后,就已经不再是原来的普通函数了,它被替换成了property类的实例 - 这个实例本身不会主动执行它持有的getter、setter逻辑,只有当Python的描述符协议被触发(也就是对类的实例发起对应属性的读、写、删除操作)时,这些存起来的函数才会被调用
你可以给代码加打印语句直观验证这个流程:
def typed_property(name, expected_type): storage_name = '_' + name print(f"类定义阶段:正在为{name}生成类型校验描述符") @property def prop(self): print(f"实例操作阶段:读取{name}属性时触发getter逻辑") return getattr(self, storage_name) @prop.setter def prop(self, value): print(f"实例操作阶段:给{name}属性赋值时触发setter逻辑") if not isinstance(value, expected_type): raise TypeError(f'{name} must be a {expected_type}') setattr(self, storage_name, value) return prop
运行后可以观察到:类定义刚执行完,控制台只会输出两行生成描述符的打印;直到你创建Person实例、访问实例属性时,才会看到getter、setter被触发的输出。
内容的提问来源于stack exchange,提问作者peal888
相关产品推荐
相关产品推荐

