Python __call__ 方法
最后修改日期:2025年3月25日
本综合指南探讨了 Python 的 __call__ 方法,该方法使实例能够像函数一样被调用。我们将通过详细的实际示例来研究它的目的、用例和高级模式。
理解 __call__ 基础
__call__ 方法允许将类实例像函数一样调用。通过在类中定义 __call__,其实例将成为可调用对象。
class Adder:
"""A class that adds a fixed value to an input."""
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
add5 = Adder(5)
print(add5(10)) # Output: 15
在这个基本示例中,我们创建了一个 Adder 类,它在初始化期间存储一个数字 n。 __call__ 方法接受一个参数 x 并返回 n 和 x 的总和。然后我们用 5 实例化 add5,并用 10 调用它,调用 __call__ 产生 15。
__call__ 方法使实例的行为类似于函数。它可以接受任意数量的参数,类似于常规函数,并通过实例属性维护状态。每当使用括号调用实例时,它都会被触发。
有状态的函数对象
__call__ 方法允许创建在调用之间保留状态的类似函数的对象。此示例演示了一个计数器,用于跟踪它被调用的次数。
class CallCounter:
"""Tracks the number of times it’s called."""
def __init__(self):
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call {self.count}")
return f"Result from call {self.count}"
counter = CallCounter()
print(counter()) # Call 1
print(counter()) # Call 2
print(counter()) # Call 3
此示例展示了几个关键概念。 CallCounter 通过其 count 属性维护状态,每次调用时都会递增它。 __call__ 方法使用 *args 和 **kwargs 接受可变参数,模仿常规函数行为,并且实例在调用之间保留其状态。
这种方法具有实际应用。它可用于跟踪和记录函数调用、实现记忆或缓存装饰器、创建有状态的函数包装器以及设计保留先前调用记忆的回调。
使用 __call__ 创建装饰器
__call__ 方法对于构建基于类的装饰器至关重要。此示例实现了一个计时器装饰器,用于测量和记录函数的执行时间。
import time
class Timer:
"""Measures execution time of a decorated function."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
print(f"{self.func.__name__} took {end - start:.4f}s")
return result
@Timer
def calculate(n):
return sum(i * i for i in range(n))
print(calculate(1000000))
此装饰器实现在初始化期间捕获函数 func。 __call__ 方法包装该函数,测量执行前后的时间,然后打印持续时间并返回结果。在这里,calculate 有效地计算平方和。
基于类的装饰器具有多个优点。与函数装饰器不同,它们可以在调用之间维护状态,并提供了一种更简单的方法来实现复杂的装饰逻辑。可以通过 __init__ 进行配置,并且装饰逻辑与包装函数保持清晰的分离。
实现函数记忆
通过将 __call__ 与状态相结合,我们可以实现记忆来缓存函数结果。此示例优化了递归斐波那契函数。
class Memoize:
"""Caches function results for faster execution."""
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args not in self.cache:
self.cache[args] = self.func(*args)
return self.cache[args]
@Memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # Much faster with memoization
此记忆实现在实例中存储原始函数和缓存字典。 __call__ 方法使用参数作为键检查缓存中是否已存在结果,仅在必要时计算和存储新结果,然后返回缓存的值。
这种方法非常有效,因为每次递归调用都受益于记忆的结果。缓存持续存在于对同一实例的调用中,使用元组参数作为可哈希键。这通过避免冗余计算,显着提高了斐波那契等递归函数的性能。
创建仿函数(函数对象)
__call__ 方法有助于创建仿函数 - 行为类似于函数但保留状态的对象。 此示例模拟多项式求值器。
class Polynomial:
"""Evaluates a polynomial for a given x."""
def __init__(self, *coefficients):
self.coeffs = coefficients
def __call__(self, x):
return sum(coeff * (x ** i)
for i, coeff in enumerate(self.coeffs))
quadratic = Polynomial(1, 2, 1) # 1x² + 2x + 1
print(quadratic(2)) # 1*(2²) + 2*2 + 1 = 9
此多项式仿函数使用表示多项式项的可变数量的系数进行初始化。当使用 x 值调用时,__call__ 计算多项式的结果,在调用之间维护系数,并且行为类似于数学函数。
仿函数具有多个优点。它们将数据和行为组合在一个对象中,比普通函数提供更大的灵活性。它们可以作为回调传递,并且能够实现复杂的类似函数的行为,使其成为 Python 编程中的通用工具。
实现命令模式
使用 __call__,我们可以实现命令设计模式来创建可执行的命令对象。此示例控制灯的状态。
class Command:
"""Wraps a receiver method as a callable command."""
def __init__(self, receiver):
self.receiver = receiver
def __call__(self, *args):
return self.receiver(*args)
class Light:
"""Simulates a light with on/off states."""
def on(self):
print("Light is ON")
def off(self):
print("Light is OFF")
light = Light()
turn_on = Command(light.on)
turn_off = Command(light.off)
turn_on() # Light is ON
turn_off() # Light is OFF
此命令模式实现创建一个通用的 Command 类,该类在初始化期间采用接收器方法。 __call__ 方法在调用时执行接收器,从而可以创建具体的命令(如打开或关闭灯),然后像函数一样调用这些命令。
这种方法具有显着的优势。它将调用者与接收者分离,允许对命令进行排队或记录。它简化了撤消/重做功能的实现,并将命令视为一等公民,从而提高了设计的灵活性。
动态 API 端点
__call__ 方法可以创建处理各种请求的动态 API 端点。此示例模拟了一个简单的用户 API,其中包含方法处理程序。
class APIEndpoint:
"""Manages dynamic API endpoints with handlers."""
def __init__(self, name):
self.name = name
self.handlers = {}
def add_handler(self, method, handler):
self.handlers[method] = handler
def __call__(self, method, *args):
if method not in self.handlers:
raise ValueError(f"Unsupported method: {method}")
return self.handlers[method](*args)
user_endpoint = APIEndpoint("users")
user_endpoint.add_handler("GET", lambda: "User list retrieved")
user_endpoint.add_handler("POST", lambda name: f"User {name} created")
print(user_endpoint("GET")) # User list retrieved
print(user_endpoint("POST", "John")) # User John created
此 API 端点实现创建支持多种 HTTP 方法的命名端点。它使用 __call__ 通过从字典中选择并执行适当的处理程序来处理请求,从而为动态响应提供干净的接口。
这种技术具有实际应用。它对于 Web 框架路由处理、创建动态 RPC 接口、实现插件系统以及构建协议适配器非常有用,从而提供了一种灵活的方式来管理请求处理。
最佳实践和陷阱
使用 __call__ 时,请考虑以下准则
- 保持可调用对象专注 - 每个对象都应该有一个单一、明确的目的
- 记录调用签名 - 清楚地说明如何调用你的对象
- 考虑实现
__repr__- 帮助调试可调用对象 - 不要滥用
__call__- 如果一个对象在概念上不可调用,请使用常规方法 - 注意可变状态 - 可调用对象中的共享状态可能导致错误
结论
__call__ 方法是一个强大的 Python 功能,使实例能够像函数一样运行。它允许使用 obj() 语法调用对象,支持创建有状态的函数对象,并且对于基于类的装饰器至关重要。此外,它有助于实现各种设计模式,并且可用于制作灵活的动态接口。
当您需要跨调用维护状态的对象,或者当类似函数的语法增强了 API 的直观性时,请使用 __call__。
来源
作者
列出所有 Python 教程。