【发布时间】:2012-12-26 19:43:17
【问题描述】:
我最近涉足 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
【问题讨论】:
-
我不懂 Ruby,所以我不知道您关于内存泄漏的说法是否现实。这在 Python 中不是问题,也许您可以澄清这一点?
-
同意,特别是我不知道非常危险从何而来。
-
我认为这主要归结为 Python 不是 C++,Ruby 不是 C++,JavaScript 不是 C++,Perl 不是 C++,...如果你尝试在任何地方都使用仿函数,那么人们会想知道为什么你要尝试用 Python 编写 C++。我可能会说仿函数是没有 GC 和闭包的语言的杂项或实现细节,这是更动态的语言(通常)不需要的杂项。当您不需要所有这些机器时,为什么要定义一个全新的类?
-
这并不是真正的内存泄漏 - 该变量只会在返回的函数保持在作用域内时保留,并且没有理由相信它会比需要的时间长。
-
您似乎以“关闭是不好的”的心态提出了这个问题,并试图找到方法来证明这一点。是的,闭包有不好的用途,但这并不意味着它们天生就不好。
标签: python ruby closures functor