Python中注入类的可调用对象为何无法自动绑定self?如何实现?
问题描述
我们可以直接向类注入普通函数作为方法:
class MainClass: ... def simple_injected_func(self: MainClass, arg: str) -> None: print(f"simple_injected_func({arg})") MainClass.simple_injected_func = simple_injected_func main_object = MainClass() main_object.simple_injected_func("arg") # 输出: simple_injected_func(arg)
也可以通过实现__call__方法创建可调用对象:
class SimpleCallableClass: def __call__(self, arg: str) -> None: print(f"SimpleCallableClass()({arg})") simple_callable_object = SimpleCallableClass() simple_callable_object("arg") # 输出: SimpleCallableClass()(arg)
现在希望将可调用对象注入类,同时让它能访问自身所属CallableClass的成员和目标MainClass的实例成员,但测试时出现参数错误:
from inspect import signature class CallableClass: def __call__(self_, self: MainClass, arg: str) -> None: print(f"CallableClass()({arg})") callable_object = CallableClass() MainClass.callable_object = callable_object main_object = MainClass() # 查看签名 print(signature(simple_injected_func)) # 输出: (self: __main__.MainClass, arg: str) -> None print(signature(callable_object)) # 输出: (self: __main__.MainClass, arg: str) -> None print(signature(main_object.simple_injected_func)) # 输出: (arg: str) -> None print(signature(main_object.callable_object)) # 输出: (self: __main__.MainClass, arg: str) -> None # 调用测试 main_object.simple_injected_func("my arg") # 输出: simple_injected_func(my arg) main_object.callable_object("my arg") # 报错: TypeError: CallableClass.__call__() missing 1 required positional argument: 'arg'
原因分析
Python的方法自动绑定机制仅针对函数对象:
- 当普通函数被赋值为类属性后,实例访问该属性时,Python会自动将其包装为绑定方法(bound method),自动把实例作为第一个参数(
self)传入函数。 - 但可调用对象(实现了
__call__的类实例)是独立对象,赋值给类属性后,实例访问它时只会直接返回对象本身,不会触发方法绑定逻辑。此时调用它时,Python只会把传入的参数传给__call__方法,但__call__定义需要两个参数(self_和self: MainClass),而我们只传了一个"my arg",因此参数数量不匹配报错。
解决方案
方案1:利用描述符协议实现自动绑定
通过实现描述符的__get__方法,在实例访问属性时自动将可调用对象包装为绑定方法:
import types from inspect import signature class MainClass: pass class CallableClass: def __call__(self_, self: MainClass, arg: str) -> None: # self_是CallableClass的实例,self是MainClass的实例 print(f"CallableClass实例: {self_}") print(f"MainClass实例: {self}") print(f"传入参数: {arg}") # 实现描述符类,用于自动绑定 class MethodBinder: def __init__(self, callable_obj): self.callable_obj = callable_obj def __get__(self, instance, owner): if instance is None: return self.callable_obj # 将MainClass实例绑定为__call__的第二个参数 return types.MethodType(self.callable_obj.__call__, instance) callable_object = CallableClass() # 用描述符包装后注入类 MainClass.callable_object = MethodBinder(callable_object) main_object = MainClass() # 查看签名,现在和普通方法一致 print(signature(main_object.callable_object)) # 输出: (arg: str) -> None # 调用测试 main_object.callable_object("my arg") # 输出: # CallableClass实例: <__main__.CallableClass object at 0x...> # MainClass实例: <__main__.MainClass object at 0x...> # 传入参数: my arg
方案2:将可调用对象包装为函数
使用自定义函数包装可调用对象,让Python的方法绑定机制生效:
import functools from inspect import signature class MainClass: pass class CallableClass: def __call__(self_, self: MainClass, arg: str) -> None: print(f"CallableClass()({arg})") callable_object = CallableClass() # 包装成函数,让Python自动绑定MainClass的self def wrapped_func(self: MainClass, arg: str): return callable_object(self, arg) MainClass.callable_object = wrapped_func main_object = MainClass() print(signature(main_object.callable_object)) # 输出: (arg: str) -> None main_object.callable_object("my arg") # 输出: CallableClass()(my arg)
方案对比
- 方案1(描述符):更灵活可复用,适合批量注入可调用对象的场景,能完整保留可调用对象的状态。
- 方案2(函数包装):实现简单代码量少,适合单次注入的场景。
内容的提问来源于stack exchange,提问作者Hans
相关产品推荐
相关产品推荐

