编程Python 开发者

解释 Python 中 __call__ 方法的工作原理,它的应用场景是什么,以及实现了 __call__ 的对象与普通函数有什么区别。

用 Hintsage AI 助手通过面试

回答。

问题的背景:

在 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 的对象保持内部状态(与普通函数不同)。
  • 可以像函数一样调用,但可以拥有类的方法和属性。
  • 允许创建更富表现力的设计模式(例如策略、命令)。

充满陷阱的问题。

call 方法是否继承了普通函数的属性——例如 namedoc

不,带有 call 方法的对象将缺少 name 属性(或者从类中获取)。函数的元数据不会被保存。

实现了 call 的对象算不算真正的函数?

不,这是类的实例,而不是函数。它只实现了 "可调用" 的行为。例如,通过 isinstance(obj, types.FunctionType) 的方式比较它与函数,将会返回 False。

能否对具有 call 的对象应用专门为函数设计的装饰器?

通常这些装饰器期待的正是函数,而不是对象(例如,functools.lru_cache)。使用可能会导致错误或者根本无法工作。

常见错误和反模式

优点:

  • 设计模式的灵活性。
  • 将数据和行为结合的能力。 缺点:
  • 代码对于初学 Python 的开发者变得不那么透明。
  • 丧失函数的标准属性;可能无法使用某些工具/装饰器。

现实生活中的例子

负面案例:在项目中通过具有 call 的类实现了记录器,以存储设置(级别、文件名)。但忘记函数信号处理器确实需要函数,因此在注册处理器时出现了错误(object is not a function)。

优点:灵活配置记录器。 缺点:与预期接口不兼容。

正面案例:在另一个项目中使用具有 call 的类创建复杂的装饰器函数,保留参数,从而简化了测试。

优点:可扩展性、便利性。 缺点:与函数或 lambda 相比代码更多。