Python类中staticmethod作为类变量回调触发不可调用报错
问题核心成因
@staticmethod装饰器本身不是可调用的函数,它的本质是把原函数包装为staticmethod类的实例,这个类实现了Python的描述符协议——只有当你通过类或者类实例访问被staticmethod装饰的属性时,描述符的解包逻辑才会触发,返回真正可调用的底层函数。- 代码中在类内部执行
cvar = Option(txt='doit', callback=_doit)时,_doit直接指向刚被staticmethod包装完的原始包装对象,全程没有触发任何描述符解包流程,赋值给callback的根本不是可调用函数,而是那个无法直接调用的staticmethod实例本身。这就是执行w1.cvar.callback()时抛出TypeError: 'staticmethod' object is not callable的直接原因。 w1._doit()、Window._doit()能正常运行,是因为这两种访问方式都会触发描述符协议,自动完成解包拿到可调用函数,和常规调用静态方法的逻辑完全一致。- 去掉
@staticmethod装饰后三种调用都不报错的原因:没有装饰器的情况下,类定义块内的_doit本身就是可调用的普通函数对象,赋值给callback的就是真实可调用的函数,自然不会出现调用失败的问题。
可行解决方法
- 方法一:主动解包包装对象。按照Python装饰器的通用规范,所有被装饰的对象都会通过
__wrapped__属性指向原始未被包装的函数,因此可以把赋值行修改为:cvar = Option(txt='doit', callback=_doit.__wrapped__)
这样拿到的就是真正可调用的底层函数,不会持有无法直接调用的staticmethod包装对象。 - 方法二:延后赋值时机。等类结构完全定义完成后,再通过类属性访问的方式获取解包后的静态方法,避免在类定义阶段直接引用被包装的对象。示例:
class Window: @staticmethod def _doit(a=1): print('do it with a', a) cvar = Option(txt='doit', callback=None) # 类定义完成后再赋值,此时通过类访问_doit会触发描述符解包 Window.cvar.callback = Window._doit - 方法三:如果不需要静态方法的装饰特性,可以直接将底层函数定义在类外部,或者在类内部不使用
@staticmethod装饰。此时类定义块内引用的就是原始可调用函数,赋值给回调参数时不会出现包装对象的问题。
内容的提问来源于stack exchange,提问作者Richard
相关产品推荐
相关产品推荐

