类装饰器中*args与**kwargs为何消失?装饰器作用域疑问
嘿,我来帮你把这几个装饰器的困惑掰明白——当初我学装饰器的时候也踩过一模一样的坑,太懂这种“怎么跟我想的不一样”的懵圈感了!
疑问1:装饰器中首次打印显示空容器?
这核心是装饰器的执行时机搞反了!很多人以为装饰器是在调用被装饰函数的时候才跑,但实际上:
当你用
@装饰器语法给函数打标签的时候,Python就已经执行了装饰器函数,把被装饰的函数传进去处理了——这一步是在函数定义阶段,而不是调用阶段!
举个直观的例子:
def my_decorator(func): # 这行代码在你写@my_decorator的时候就执行了! print("装饰器初始化:当前args/kwargs是空的 ->", [], {}) def wrapper(*args, **kwargs): # 这行才是调用被装饰函数时执行的 print("函数调用时:参数是 ->", args, kwargs) return func(*args, **kwargs) return wrapper @my_decorator def say_hello(name): print(f"Hello {name}!") # 现在还没调用say_hello呢,你已经能看到装饰器初始化的打印了! say_hello("Alice")
所以首次打印的空容器,是因为装饰器在绑定函数的时候,根本还没收到任何调用参数——那些参数要等你真正调用被装饰函数时,才会被wrapper接住。
疑问2:错误写法的build()里,为啥调用前*args/**kwargs看似不存在,调用后又“回来”?
你说的“会失败的形式”,大概率是闭包变量被内层函数的参数名遮蔽了!比如这种写法:
def build(): # 外层定义的空容器 outer_args = [] outer_kwargs = {} def wrapper(*args, **kwargs): # 注意!这里的args/kwargs是wrapper的局部参数,和外层的outer_args完全是两个东西! print("wrapper内部的参数:", args, kwargs) wrapper("test", age=20) # 这里打印的是外层的变量,当然还是空的! print("build函数里的外层变量:", outer_args, outer_kwargs) build()
你觉得“调用后回到作用域”其实是误解:内层wrapper里的args/kwargs是它自己的局部变量,调用wrapper时会临时赋值,但调用结束就销毁了,和外层的半毛钱关系都没有。如果要让内层函数修改外层的变量,要么别用同名参数,要么用nonlocal声明:
def build(): outer_args = [] outer_kwargs = {} def wrapper(*new_args, **new_kwargs): nonlocal outer_args, outer_kwargs outer_args = new_args outer_kwargs = new_kwargs print("wrapper内部的参数:", new_args, new_kwargs) wrapper("test", age=20) print("build函数里的外层变量:", outer_args, outer_kwargs) # 现在就有值了! build()
疑问3:类装饰器中的*args和**kwargs为何看似“消失”?
类装饰器的逻辑和函数装饰器不一样,得分清楚两个核心方法的作用:
__init__:当你用@类装饰器的时候执行,它只接收被装饰的函数作为参数——这时候还没调用函数,自然没有args/kwargs。__call__:当你调用被装饰后的函数时执行,这时候才会接收调用时的*args和**kwargs。
举个例子看清楚:
class MyClassDecorator: def __init__(self, func): # 这里只有被装饰的函数,没有调用参数! print("类装饰器初始化:只拿到了函数", func.__name__) self.func = func def __call__(self, *args, **kwargs): # 这里才是真正接收调用参数的地方! print("调用被装饰函数时的参数:", args, kwargs) return self.func(*args, **kwargs) @MyClassDecorator def greet(name): print(f"Hi {name}!") # 初始化阶段没有参数,调用时才会出现 greet("Bob")
如果你的类装饰器是带参数的(比如@MyClassDecorator(level="debug")),逻辑会更绕一点:__init__接收装饰器自己的参数,然后返回一个接收被装饰函数的对象,__call__还是负责处理调用参数。这时候要是在__init__里找args/kwargs,肯定也是找不到的——因为那时候连被装饰的函数都还没绑定呢!
内容的提问来源于stack exchange,提问作者pstatix
相关产品推荐
相关产品推荐

