在许多情况下,有两种实现选择:闭包和可调用类。例如,
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')
在任何一个方向上做出选择时应该考虑哪些因素?
我能想到两个:
似乎关闭总会有更好的表现(不能 想一个反例)。
我认为有些情况下关闭无法完成工作(例如,如果 其状态随时间而变化。)
我在这些方面是否正确?还有什么可以添加?
答案 0 :(得分:10)
闭包更快。类更灵活(即可用的方法多于__call __)。
答案 1 :(得分:3)
我意识到这是一个较旧的帖子,但我没有看到列出的一个因素是在Python(pre-nonlocal)中你不能修改引用环境中包含的局部变量。 (在你的例子中,这样的修改并不重要,但从技术上讲,缺乏能够修改这样的变量意味着它不是真正的闭包。)
例如,以下代码不起作用:
def counter():
i = 0
def f():
i += 1
return i
return f
c = counter()
c()
上面对 c 的调用将引发UnboundLocalError异常。
使用mutable(如字典)很容易解决这个问题:
def counter():
d = {'i': 0}
def f():
d['i'] += 1
return d['i']
return f
c = counter()
c() # 1
c() # 2
但当然这只是一种解决方法。
答案 2 :(得分:1)
我认为课程方法一目了然更容易理解,因此更易于维护。由于这是优秀Python代码的前提之一,我认为所有事情都是平等的,最好使用类而不是嵌套函数。这是Python的灵活性使得语言违反“应该有一个,最好只有一个,明显的做某种方式”的方法的情况之一。用于Python编码的谓词。
任何一方的性能差异都应该可以忽略不计 - 如果你的代码中性能很重要,你当然应该对其进行分析并优化相关部分,可能会将一些代码重写为本机代码。
但是,是的,如果使用状态变量存在紧密循环,那么评估闭包变量应该比评估类属性要快一些。当然,这可以通过在进入循环之前在类方法中插入类似op = self.op
的行来克服,使循环内的变量访问变为局部变量 - 这将避免属性外观 - 为每次访问提取和获取。同样,性能差异应该可以忽略不计,如果您需要这么多额外的性能并且使用Python进行编码,那么您会遇到更严重的问题。
答案 3 :(得分:1)
请注意,由于我之前在测试代码中发现的错误,我的原始答案不正确。修订后的版本如下。
我做了一个小程序来测量运行时间和内存消耗。我创建了以下可调用类和闭包:
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个参数。
我在Linux x64上使用了CPython 2.7.10和3.4.3+。我只能在Python 2上进行内存分析。我使用的源代码可用here。
我的结论是:
math.pow()
大致相同,而通过可调用类它大概翻了两倍。这些是非常粗略的估计,它们可能因硬件,操作系统以及您进行比较的功能而有所不同。但是,它让您了解使用每种可调用的影响。
因此,这支持(与之前我所写的相反),@ RaymondHettinger给出的接受的答案是正确的,并且对于间接呼叫,闭包应该是首选,至少只要它没有'妨碍可读性。另外,感谢@AXO指出原始代码中的错误。
答案 4 :(得分:-1)
我会用以下内容重写class
示例:
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)
使用Python 3.2.2在我的机器上给出了0.40 usec / pass(函数0.31,因此慢了29%)。不使用object
作为基类,它给出0.65 usec / pass(比基于object
慢55%)。由于某些原因,在op
中检查__call__
的代码提供的结果几乎与在__init__
中完成的结果相同。以object
为基础,在__call__
内检查得到0.61 usec / pass。
您使用类的原因可能是多态性。
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