类方法使用inspect.signature时,直接调用与装饰器内调用的Signature.bind().arguments结果差异问题
类方法使用inspect.signature时,直接调用与装饰器内调用的Signature.bind().arguments结果差异问题
这是个非常典型的Python装饰器与描述器(classmethod本质是描述器)交互的问题,核心在于装饰器应用顺序和**classmethod对函数签名的隐式调整**,我来一步步帮你理清:
1. 装饰器中拿到的是原始的未被classmethod处理的函数
先看你代码里的装饰器顺序:
@classmethod @decorator def mymethod(cls, a, b): return a + b
Python的装饰器是从下往上执行的:先执行@decorator装饰原始的mymethod函数(这个函数的签名是(cls, a, b)),得到inner函数;再执行@classmethod装饰inner,把它转换成类方法。
所以在装饰器的inner函数里:
func变量指向的是原始的未绑定函数(包含cls参数的那个)- 当你调用
MyClass.mymethod(1,2)时,classmethod会自动把MyClass作为第一个参数传递给inner,也就是说inner收到的*args实际是(MyClass, 1, 2) - 用这个参数列表去绑定原始函数的签名
(cls, a, b),自然就得到了包含cls的完整参数映射:{'cls': <class '__main__.MyClass'>, 'a': 1, 'b': 2}
2. 直接访问MyClass.mymethod得到的是经过classmethod处理的绑定方法
当你直接写MyClass.mymethod时,拿到的不是原始函数,而是classmethod这个描述器返回的绑定了类的可调用对象。这个对象的签名已经被classmethod隐式调整了:
- 它会自动把当前类作为第一个参数注入到底层函数中
- 对外暴露的签名只保留业务参数
a和b,不需要你手动传递cls
这就解释了两个现象:
inspect.signature(MyClass.mymethod).bind(1, 2)能成功,返回{'a': 1, 'b': 2}——因为签名里已经没有cls参数了inspect.signature(MyClass.mymethod).bind(MyClass, 1, 2)报错“参数过多”——因为这个绑定方法的签名只接受2个位置参数,你多传了一个MyClass
3. 验证结论的小实验
如果你想在外部拿到和装饰器内一致的签名结果,可以通过函数的__wrapped__属性逐层获取原始函数(因为经过了两层装饰:classmethod和decorator):
import inspect import functools def decorator(func): @functools.wraps(func) def inner(*args, **kwargs): print(inspect.signature(func).bind(*args, **kwargs).arguments) return func(*args, **kwargs) return inner class MyClass: @classmethod @decorator def mymethod(cls, a, b): return a + b # 逐层获取原始函数 original_func = MyClass.mymethod.__wrapped__.__wrapped__ # 用原始函数签名绑定参数 print(inspect.signature(original_func).bind(MyClass, 1, 2).arguments) # 输出: {'cls': <class '__main__.MyClass'>, 'a': 1, 'b': 2}
总结
- 装饰器内部的
func是原始未被classmethod处理的函数,签名包含cls,且classmethod会自动注入类作为第一个参数,所以绑定结果包含cls - 直接访问的
MyClass.mymethod是**classmethod生成的绑定方法**,签名已隐式隐藏cls,只需要传递业务参数
备注:内容来源于stack exchange,提问作者cmsommerville
相关产品推荐
相关产品推荐

