Python装饰器中succ.calls的定义、作用域及相关技术疑问
关于Python函数属性与装饰器的疑问解答
问题背景
你遇到的装饰器代码片段:
def call_counter(func): def helper(x): helper.calls += 1 return func(x) helper.calls = 0 return helper @call_counter def succ(x): return x + 1 print(succ.calls) for i in range(10): succ(i) print(succ.calls)
你对succ.calls的定义、作用域感到困惑,同时自己尝试的代码出现属性错误:
def do_something(): do_something.my_var = 6 print(do_something.my_var)
核心解释:Python函数是一等对象,支持绑定属性
这种技术叫做函数属性(Function Attributes),Python中所有函数都是对象,你可以像给普通类实例添加属性一样,给函数对象绑定自定义属性,用来存储与函数相关的状态。
1. succ.calls的本质
装饰器@call_counter的执行逻辑是:
- 当你用
@call_counter装饰succ时,Python会自动把原succ函数作为参数传入call_counter。 call_counter内部定义了helper函数,然后给helper绑定了calls属性并初始化为0。call_counter返回helper函数,此时全局变量succ不再指向原来的succ,而是指向helper这个函数对象。- 因此
succ.calls其实就是helper.calls,调用succ(i)时执行的是helper(x),里面会累加helper.calls,也就是succ.calls。
2. 测试代码报错原因
你写的do_something.my_var是在函数内部定义的,只有当你调用do_something()之后,这个属性才会被创建并绑定到do_something函数对象上。直接执行print(do_something.my_var)时,函数还没被调用,属性不存在,所以触发AttributeError。
修正后的代码可以正常运行:
def do_something(): do_something.my_var = 6 do_something() print(do_something.my_var) # 输出6
函数属性的作用域
函数属性的作用域就是函数对象本身,只要函数对象在内存中存在,你就可以通过函数名.属性名的方式访问它,不管是在函数内部还是外部。
内容的提问来源于stack exchange,提问作者Mike Wilson
相关产品推荐
相关产品推荐

