Python 函数闭包与可调用类
在许多情况下,有两种实现选择:闭包和可调用类。比如说,Python 函数闭包与可调用类,python,performance,python-3.x,closures,Python,Performance,Python 3.x,Closures,在许多情况下,有两种实现选择:闭包和可调用类。比如说, 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(
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')
或
在选择哪个方向时,应该考虑什么因素?< /P>
我可以想到两个:
- 似乎闭包总是有更好的性能(不能 想一个反例)
- 我认为有些情况下,关闭无法完成工作(例如,如果 其状态随时间而变化)
我说的对吗?还有什么可以补充的? 我认为类方法更容易理解一眼,因此,更可维护。由于这是优秀Python代码的前提之一,我认为在所有条件相同的情况下,最好使用类而不是嵌套函数。在这种情况下,Python的灵活性使得该语言违反了Python编码的“应该有一种,最好只有一种明显的方式”谓词 任何一方的性能差异都应该可以忽略不计——如果您的代码在这个级别上的性能很重要,那么您当然应该对其进行分析并优化相关部分,可能会将一些代码重写为本机代码
但是,如果存在使用状态变量的紧密循环,那么评估闭包变量应该比评估类属性稍微快一些。当然,在进入循环之前,只需在类方法中插入一行,如
op=self.op
,就可以克服这一问题,使循环中的变量访问成为局部变量-这将避免对每次访问进行属性查找和获取。同样,性能差异应该是可以忽略的,如果您需要这一点点的额外性能,并且使用Python编码,那么您会遇到更严重的问题。闭包更快。类更灵活(也就是说,比仅仅调用更多的方法可用)。我将用以下内容重新编写类
示例:
class F(object):
__slots__ = ('__call__')
def __init__(self, op):
if op == 'mult':
self.__call__ = lambda a, b: a * b
elif op == 'add':
self.__call__ = lambda a, b: a + b
else:
raise InvalidOp(op)
在我使用Python3.2.2的机器上,这将提供0.40usec/pass(函数0.31,因此速度降低29%)。在不使用对象
作为基类的情况下,它提供0.65 usec/次(即比基于对象
的速度慢55%)。由于某种原因,在\uuuuu call\uuuuu
中检查op
的代码给出的结果几乎与在\uuuuu init\uuuuu
中执行的结果相同。以对象
为基础,在内检查调用
给出0.61 usec/次
使用类的原因可能是多态性
class UserFunctions(object):
__slots__ = ('__call__')
def __init__(self, name):
f = getattr(self, '_func_' + name, None)
if f is None: raise InvalidOp(name)
else: self.__call__ = f
class MyOps(UserFunctions):
@classmethod
def _func_mult(cls, a, b): return a * b
@classmethod
def _func_add(cls, a, b): return a + b
我意识到这是一篇较旧的文章,但我没有看到列出的一个因素是,在Python(pre-nonlocal)中,您不能修改引用环境中包含的局部变量。(在您的示例中,这样的修改并不重要,但从技术上讲,无法修改这样的变量意味着它不是真正的闭包。) 例如,以下代码不起作用:
def counter():
i = 0
def f():
i += 1
return i
return f
c = counter()
c()
调用上面的c将引发未绑定的LocalError异常
这很容易通过使用可变表(如字典)来实现:
def counter():
d = {'i': 0}
def f():
d['i'] += 1
return d['i']
return f
c = counter()
c() # 1
c() # 2
当然,这只是一个解决办法。请注意,由于之前在测试代码中发现的错误,我的原始答案不正确。修订版如下。 我制作了一个小程序来测量运行时间和内存消耗。我创建了以下可调用类和闭包:
class CallMe:
def __init__(self, context):
self.context = context
def __call__(self, *args, **kwargs):
return self.context(*args, **kwargs)
def call_me(func):
return lambda *args, **kwargs: func(*args, **kwargs)
我对接受不同数量参数的简单函数的调用进行计时(math.sqrt()
带有1个参数,math.pow()
带有2个参数,max()
带有12个参数)
我在Linuxx64上使用了CPython2.7.10和3.4.3+。我只能在Python2上进行内存分析。我使用的源代码是可用的
我的结论是:
- 闭包的运行速度比等价的可调用类快:在Python2上大约快3倍,但在Python3上只快1.5倍。缩小的原因既有闭包变慢,也有可调用类变慢
- 闭包比等价的可调用类占用更少的内存:大约占内存的2/3(仅在Python2上测试)
- 虽然不是原始问题的一部分,但值得注意的是,通过闭包进行调用的运行时开销与调用
大致相同,而通过可调用类进行调用的运行时开销大约是前者的两倍math.pow()
因此,这支持了@RaymondHettinger给出的公认答案是正确的(与我之前所写的相反),并且对于间接调用,闭包应该是首选的,至少只要它不影响可读性。另外,感谢@AXO指出了我原始代码中的错误。“使用什么更好”?请定义您试图优化的标准。擅长什么?更小更快?更多使用Oracle许可产品?你说“更好”是什么意思?@max,实际上闭包可以有状态(它们可以捕获周围的任何东西,包括局部变量)。@S.Lott,据我所知,这个问题类似于:“为什么你更喜欢可调用类而不是闭包?”另请参见:@S.Lott:我实际上是想知道我将来应该怎么想,每当这个问题出现时。所以我不想把讨论局限于一种特定的情况。我想,我想考虑的方面是性能、清晰度、灵活性和可靠性。至少我曾经说过,代码的优化可能会被使用的时间提升。但是,在大多数情况下,高级算法有更大的潜力来获得更高的性能。这些函数确实通过使用cpu时间显示在分析器的顶部,因为它们重复了很多次。我在想
class CallMe:
def __init__(self, context):
self.context = context
def __call__(self, *args, **kwargs):
return self.context(*args, **kwargs)
def call_me(func):
return lambda *args, **kwargs: func(*args, **kwargs)