为何手动添加到functools.wraps()描述符的__set_name__钩子未被调用?
为何为functools.wraps生成的装饰器添加__set_name__钩子后未被调用?
问题重现
你尝试为装饰器返回的函数添加__set_name__钩子,但该钩子从未被调用,也无报错:
import functools def wrap(fn): """Decorator.""" @functools.wraps(fn) def w(*args, **kwargs): return fn(*args, **kwargs) # This never gets called. def set_name(self, obj, name): print(f"inside __set_name__: {self=}, {obj=}, {name=}") w.__set_name__ = set_name.__get__(w) return w class Foo: @wrap def foo(self): pass
原因分析
问题出在你对__set_name__的绑定方式上:
- __set_name__是描述符的钩子方法,Python在类创建阶段调用它时,会自动将描述符实例(即装饰后的函数w)作为第一个参数(self)传入,无需手动绑定。
- 你通过
set_name.__get__(w)将set_name绑定成了w的绑定方法,这会导致参数传递逻辑冲突:当Python尝试调用w.__set_name__(Foo, 'foo')时,绑定方法的参数接收逻辑与Python内部的调用逻辑不匹配,最终导致钩子未被触发。 - 此外,Python对函数类型的描述符,仅会调用其__set_name__属性中存储的未绑定可调用对象(比如普通函数),绑定方法会被跳过。
解决办法
直接将set_name函数赋值给w.set_name,去掉手动绑定的操作:
import functools def wrap(fn): """Decorator.""" @functools.wraps(fn) def w(*args, **kwargs): return fn(*args, **kwargs) def set_name(self, obj, name): print(f"inside __set_name__: {self=}, {obj=}, {name=}") # 直接赋值函数,无需手动绑定 w.__set_name__ = set_name return w class Foo: @wrap def foo(self): pass
运行修改后的代码,Foo类创建时会正常触发__set_name__钩子,输出预期内容。
补充说明
functools.wraps本身不会干扰__set_name__的调用,它仅负责将原函数的元数据(如名称、文档字符串等)复制到装饰后的函数上。你之前的误解在于手动绑定self参数,而实际上Python会自动完成描述符钩子方法的参数传递。
内容的提问来源于stack exchange,提问作者Richard Hansen
相关产品推荐
相关产品推荐

