问题的背景:
在 Python 中,一切都是对象,包括任何函数。已经实现了一个机制,使得类的实例可以像函数一样工作。为此,引入了特殊的魔法方法 call,使得对象可以被调用。
问题:
有时需要传递一个可以执行调用操作的对象,例如有状态的函数(stateful functions)、闭包、事件处理对象等。架构设计需要理解如何正确实现这种功能。
解决方案:
通过在类中实现 call 方法,可以使其实例像 "函数" 一样可调用。这使得可以将类的功能(状态的封装、继承、方法)与函数的可调用性捆绑在一起。这种方法用于创建命令对象、复杂的处理器、包装等。
代码示例:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # 输出 15
关键特征:
call 方法是否继承了普通函数的属性——例如 name 和 doc?
不,带有 call 方法的对象将缺少 name 属性(或者从类中获取)。函数的元数据不会被保存。
实现了 call 的对象算不算真正的函数?
不,这是类的实例,而不是函数。它只实现了 "可调用" 的行为。例如,通过 isinstance(obj, types.FunctionType) 的方式比较它与函数,将会返回 False。
能否对具有 call 的对象应用专门为函数设计的装饰器?
通常这些装饰器期待的正是函数,而不是对象(例如,functools.lru_cache)。使用可能会导致错误或者根本无法工作。
优点:
负面案例:在项目中通过具有 call 的类实现了记录器,以存储设置(级别、文件名)。但忘记函数信号处理器确实需要函数,因此在注册处理器时出现了错误(object is not a function)。
优点:灵活配置记录器。 缺点:与预期接口不兼容。
正面案例:在另一个项目中使用具有 call 的类创建复杂的装饰器函数,保留参数,从而简化了测试。
优点:可扩展性、便利性。 缺点:与函数或 lambda 相比代码更多。