闭包:什么是好用例?为什么不是仿函数?是否值得消极?

时间:2012-12-26 19:43:17

标签: python ruby closures functor

我最近潜入Python。以前,我用C ++和Matlab编写了大部分数值和数据分析代码。我看到了很多关于Python和Ruby以及闭包的讨论。几乎所有的例子都是这样的:

>>> def makeAdder(y):
...  def myAdder(x):
...   return x + y
...  return myAdder
... 
>>> f = makeAdder(10)
>>> f(5)
15

我知道这在某种意义上可能有用。但是,实际上,这种情况下的行为(“只读”情况)很容易被对象(仿函数)模仿:

>>> class MyAdder(object):
...  def __init__(self,y):
...   self.y = y
...  def __call__(self,x):
...   return self.y + x
... 
>>> f = MyAdder(5)
>>> f(10)
15

该对象不占用更多的代码空间,而且它的用途更广泛。跟踪和调试后续代码也更容易。

在这种情况下,我们只读取非局部变量。但是我们也可以写它:在Ruby中,当然,在Python中使用nonlocal关键字。当然,对象也支持它。但是对于该对象,您将数据捆绑在一起,这样您就可以准确地知道发生了什么。闭包可能以完全不透明的方式携带变量,这可能导致代码难以调试。这是一个非常奇怪的例子:

irb(main):001:0> def new_counter
irb(main):002:1> x = 0
irb(main):003:1> lambda { x +=1 }
irb(main):004:1> end
=> nil
irb(main):005:0> counter_a = new_counter
=> #<Proc:0x00007f85c6421cd0@(irb):3>
irb(main):006:0> counter_a.call
=> 1
irb(main):007:0> counter_a.call
=> 2

至少对我来说,这种行为是不直观的。它还有可能导致内存泄漏。这会让你有很多绳子可以自己挂起来。同样,在Ruby中尤其如此,您不需要显式地启用它(与Python不同),因为在Ruby中,它的所有主代码都有块,它们可以访问所有内容。如果外部变量因为处于闭包中而发生变化,那么如果你绕过那个闭包,你可以让变量无限期地改变,并且远离它所居住的地方。与始终安全地携带数据的对象形成对比。

为什么你会听到很多关于闭包有多好的讨论,以及它们应该如何被包含在Java中,当它们没有完全使用Python时它是如何吸收的等等?为什么不使用仿函数?或重构代码以避免,因为它们有多么危险?只是为了澄清一下,我不是那些在口腔OO类型中起泡的人之一。我是否低估了他们的使用,夸大了他们的危险,或两者兼而有之?

编辑:也许我应该区分三件事:只读一次的闭包(这是我的例子显示的,几乎每个人都讨论过),一般读取的闭包,以及写入的闭包。如果你使用外部函数的局部变量在另一个函数中定义一个函数,那么它几乎不会再回来困扰你。该空间中的变量无法以我能想到的任何方式访问,因此您无法更改它。这是非常安全的,并且是一种方便(可能比仿函数)生成函数的方法。 另一方面,如果在类方法内或主线程内创建闭包,则每次调用时都会读入变量,可以从其他位置访问。所以它可以改变。我认为这是危险的,因为关闭的变量不会出现在函数头中。您可以在代码的第1页上说一个关闭主线程变量x的长闭包,然后根据无关原因修改x。然后重新使用闭包并获得您不理解的奇怪行为,这可能很难调试。 如果您实际上写入封闭变量,那么就像我的Ruby示例所示,您确实有可能造成混乱并导致意外行为。

Edit2:我给出了一个关于闭包的奇怪行为的例子,用于第三种用法,写入非局部变量。以下是第二种用法中的奇怪(并非坏)行为的示例(在范围中定义闭包变量可以修改的闭包):

>>> fs = [(lambda n: i + n) for i in range(10)]
>>> fs[4](5)
14

1 个答案:

答案 0 :(得分:10)

可读性。您的Python示例显示了闭包版本与仿函数相比更加明显且易于阅读的内容。

我们还巧妙地避免创建一个什么都不做但只是像功能一样的类 - 这有点冗余。

如果没有别的,当我们某事时,it makes sense to describe it as an action, not an object

另外需要注意的是,在Python中使用这些结构的示例 lot ,效果很好,就是装饰器。大多数函数修饰器都是这样做的,它们是一个非常有用的功能。

编辑:作为关于状态的注释,请记住Python中的函数并不特殊,它们仍然是对象:

>>> def makeAdder(y):
...     def myAdder(x):
...         return x + myAdder.y
...     myAdder.y = y
...     return myAdder
... 
>>> f = makeAdder(10)
>>> f(5)
15
>>> f.y
10