函数闭包 vs. 可调用类

19 投票
6 回答
7131 浏览
提问于 2025-04-17 10:52

在很多情况下,我们有两种实现的选择:闭包和可调用类。比如说,

class F:
  def __init__(self, op):
    self.op = op
  def __call__(self, arg1, arg2):
    if (self.op == 'mult'):
      return arg1 * arg2
    if (self.op == 'add'):
      return arg1 + arg2
    raise InvalidOp(op)

f = F('add')

或者

def F(op):
  if op == 'or':
    def f_(arg1, arg2):
      return arg1 | arg2
    return f_
  if op == 'and':
    def g_(arg1, arg2):
      return arg1 & arg2
    return g_
  raise InvalidOp(op)

f = F('add')

在选择这两者时,有哪些因素需要考虑呢?

我能想到两个:

  • 闭包的性能似乎总是更好(我想不出反例)。

  • 我觉得有些情况下闭包无法完成任务(例如,当它的状态随着时间变化时)。

我这样说对吗?还有什么可以补充的吗?

6 个回答

4

我知道这个帖子有点旧了,但我想提一个没有提到的点,那就是在Python(在引入nonlocal之前)中,你不能修改引用环境中的局部变量。虽然在你的例子中这种修改并不重要,但从技术上讲,不能修改这样的变量意味着它不是真正的闭包。

举个例子,下面的代码是不能运行的:

def counter():
    i = 0
    def f():
        i += 1
        return i
    return f

c = counter()
c()

上面调用c时会引发一个UnboundLocalError异常。

不过,这个问题可以通过使用可变对象,比如字典,来解决:

def counter():
    d = {'i': 0}
    def f():
        d['i'] += 1
        return d['i']
    return f

c = counter()
c()     # 1
c()     # 2

当然,这只是一个变通办法。

4

我觉得用类来写代码一眼就能看懂,所以更容易维护。因为这是写好Python代码的一个基本原则,所以在其他条件相同的情况下,使用类比用嵌套函数要好。这就是Python灵活性的一种体现,有时候会让它违背“应该有一种,最好只有一种,明显的方式来做某件事”这个原则。

在性能方面,两者的差别应该不大。如果你的代码在这个层面上性能很重要,那你肯定需要对它进行性能分析,找出需要优化的部分,甚至可能需要把一些代码重写成原生代码。

不过,如果有一个紧密的循环在使用状态变量,那么评估闭包变量的速度应该会比评估类属性稍微快一点。当然,你可以在进入循环之前,在类的方法里加一行代码,比如 op = self.op,这样在循环里的变量访问就变成了访问一个局部变量,这样就避免了每次访问都要查找属性的过程。再次强调,性能差别应该不大,如果你真的需要这么一点点额外的性能,而你又在用Python编程,那你可能还有更严重的问题。

16

闭包运行得更快。而类则更灵活(也就是说,除了 __call 方法,还有更多其他的方法可以使用)。

撰写回答