你能解释一下闭包(因为它们与 Python 有关)吗?

Can you explain closures (as they relate to Python)?

提问人:knowncitizen 提问时间:8/18/2008 最后编辑:Honest Abeknowncitizen 更新时间:5/12/2018 访问量:17747

问:

我一直在阅读很多关于闭包的文章,我想我理解它们,但为了不让自己和其他人蒙上阴影,我希望有人能尽可能简洁明了地解释闭包。我正在寻找一个简单的解释,可以帮助我了解我想在哪里以及为什么使用它们。

Python 函数式编程 闭包

评论


答:

48赞 Anders Eurenius 8/18/2008 #1

这很简单:一个函数,它引用包含范围中的变量,可能是在控制流离开该范围之后。最后一点非常有用:

>>> def makeConstantAdder(x):
...     constant = x
...     def adder(y):
...         return y + constant
...     return adder
... 
>>> f = makeConstantAdder(12)
>>> f(3)
15
>>> g = makeConstantAdder(4)
>>> g(3)
7

请注意,12 和 4 分别在 f 和 g 中“消失”了,这个特征使 f 和 g 成为正确的闭包。

评论

4赞 ShadowRanger 3/13/2020
没有必要做;你可以只在嵌套函数中做(或接收带有名称的参数),它会很好工作;闭包捕获参数与非参数局部变量没有什么不同。constant = xreturn y + xconstant
8赞 Mark Cidade 8/18/2008 #2

我从未听说过在解释什么是闭包的相同上下文中使用事务,而且这里实际上没有任何事务语义。

它之所以被称为闭包,是因为它“闭合”了外部变量(常量)——也就是说,它不仅仅是一个函数,而且是创建函数的环境的外壳。

在下面的示例中,在更改 x 后调用闭包 g 也会更改 g 中 x 的值,因为 g 在 x 上闭合:

x = 0

def f():
    def g(): 
        return x * 2
    return g


closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4

评论

0赞 Bruno Le Floch 8/28/2012
此外,就目前而言,计算但不返回任何内容。那应该是.+1 尽管如此,还是对“关闭”一词的解释。g()x * 2return x * 2
16赞 ESV 8/18/2008 #3

我喜欢这个粗略、简洁的定义

可以引用不再处于活动状态的环境的函数。

我会补充

闭包允许您将变量绑定到函数中,而无需将它们作为参数传递

接受参数的装饰器是闭包的常见用途。闭包是这种“函数工厂”的常见实现机制。我经常选择在策略模式中使用闭包,当策略在运行时被数据修改时。

在允许匿名块定义的语言中(例如,Ruby,C#),闭包可用于实现(相当于)新颖的新控制结构。缺乏匿名块是 Python 中闭包的局限性之一。

23赞 Jegschemesch 8/23/2008 #4

老实说,我非常了解闭包,只是我从来都不清楚“闭包”到底是什么,以及什么是“闭包”。我建议你放弃寻找术语选择背后的任何逻辑。

无论如何,这是我的解释:

def foo():
   x = 3
   def bar():
      print x
   x = 5
   return bar

bar = foo()
bar()   # print 5

这里的一个关键思想是,从 foo 返回的函数对象保留了对本地变量 'x' 的钩子,即使 'x' 已经超出了范围并且应该失效。这个钩子是 var 本身,而不仅仅是 var 当时的值,所以当调用 bar 时,它打印的是 5,而不是 3。

还要明确的是,Python 2.x 的闭包有限:我无法修改“bar”中的“x”,因为编写“x = bla”会在 bar 中声明一个本地“x”,而不是分配给 foo 的“x”。这是 Python 的 assignment=declaration 的副作用。为了解决这个问题,Python 3.0 引入了 nonlocal 关键字:

def foo():
   x = 3
   def bar():
      print x
   def ack():
      nonlocal x
      x = 7
   x = 5
   return (bar, ack)

bar, ack = foo()
ack()   # modify x of the call to foo
bar()   # print 7
-2赞 ConcernedOfTunbridgeWells #5

我所见过的关于闭合的最好解释是解释机制。事情是这样的:

将您的程序堆栈想象成一个退化树,其中每个节点只有一个子节点,而单个叶节点是当前正在执行的过程的上下文。

现在放宽每个节点只能有一个子节点的约束。

如果这样做,则可以有一个构造(“yield”),它可以在不丢弃本地上下文的情况下从过程返回(即,当您返回时,它不会将其从堆栈中弹出)。下次调用该过程时,调用将拾取旧的堆栈(树)帧,并从中断的位置继续执行。

评论

0赞 Jules 1/12/2009
这不是对关闭的解释。
0赞 Matthew Olenik 4/12/2009
你描述的是延续,而不是结束。
110赞 jfs 9/27/2008 #6

关闭关闭

对象是带有方法的数据 附加,闭包是具有 附加数据。

def make_counter():
    i = 0
    def counter(): # counter() is a closure
        nonlocal i
        i += 1
        return i
    return counter

c1 = make_counter()
c2 = make_counter()

print (c1(), c1(), c2(), c2())
# -> 1 2 1 2

评论

7赞 James Porter 9/20/2013
请注意,在 python 3 中添加的 python 2.x 没有完整的读写闭包(即您可以读取闭合变量,但不能更改其值)nonlocal
8赞 jfs 9/21/2013
@JamesPorter:注意:您可以在 Python 2 中使用可变对象来模拟关键字,例如,在这种情况下,您不能更改名称(将其绑定到另一个对象),您可以更改名称所指的可变对象本身。该列表是必需的,因为整数在 Python 中是不可变的。nonlocalL = [0] \n def counter(): L[0] += 1; return L[0]
1赞 James Porter 9/22/2013
@J.F.Sebastian:对。不过,这总是感觉像是一个肮脏的黑客:)
0赞 Jason Law 1/26/2023
“counter() is a closure” 不应该是 “counter is a closure” 吗?
0赞 jfs 1/26/2023
@JasonLaw正确的(如果解释为函数调用的代码,我们只会得到)。在注释(简单的英语)中,我使用了这样的约定,即 parens 之前的名称指的是一个函数(以将其与其他没有 '' markdown 或类似词的单词区分开来)。int
3赞 Naveen Michaud-Agrawal 1/24/2009 #7

下面是闭包的典型用例 - GUI 元素的回调(这将是子类化按钮类的替代方法)。例如,您可以构造一个函数,该函数将响应按钮按下而调用,并在父作用域中处理单击所需的相关变量上“关闭”。这样,你就可以从同一个初始化函数连接相当复杂的接口,将所有依赖项构建到闭包中。

0赞 Ricardo Avila 9/20/2013 #8

对我来说,“闭包”是能够记住它们被创建的环境的函数。此功能允许您在闭包内使用变量或方法,否则您将无法使用,因为它们不再存在,或者由于范围而无法访问。让我们看看 ruby 中的这段代码:

def makefunction (x)
  def multiply (a,b)
    puts a*b
  end
  return lambda {|n| multiply(n,x)} # => returning a closure
end

func = makefunction(2) # => we capture the closure
func.call(6)    # => Result equal "12"  

即使“乘法”方法和“x”变量都不再存在,它也能工作。都是因为闭合能力要记住。

3赞 Russia Must Remove Putin 7/18/2014 #9

在 Python 中,闭包是函数的实例,其变量不可变地绑定到它。

事实上,数据模型在对函数属性的描述中解释了这一点__closure__

无或包含函数自由变量绑定的单元格元组。只读

为了证明这一点,请执行以下操作:

def enclosure(foo):
    def closure(bar):
        print(foo, bar)
    return closure

closure_instance = enclosure('foo')

显然,我们知道我们现在有一个从变量名称指向的函数。从表面上看,如果我们用一个对象来调用它,它应该打印字符串,以及字符串表示的任何内容。closure_instancebar'foo'bar

事实上,字符串 'foo' 绑定到函数的实例,我们可以在这里直接读取它,通过访问属性元组中第一个(也是唯一一个)单元格的属性:cell_contents__closure__

>>> closure_instance.__closure__[0].cell_contents
'foo'

顺便说一句,C API 文档中描述了单元对象:

“Cell”对象用于实现多个引用的变量 范围

我们可以演示闭包的用法,注意它卡在函数中并且不会改变:'foo'

>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux

没有什么可以改变它:

>>> closure_instance.__closure__ = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

分部函数

给出的示例使用闭包作为部分函数,但如果这是我们唯一的目标,则可以使用相同的目标来实现functools.partial

>>> from __future__ import print_function # use this if you're in Python 2.
>>> partial_function = functools.partial(print, 'foo')
>>> partial_function('bar')
foo bar
>>> partial_function('baz')
foo baz
>>> partial_function('quux')
foo quux

还有一些更复杂的闭包不适合部分函数示例,如果时间允许,我将进一步演示它们。

1赞 thiagoh 9/23/2015 #10

下面是 Python3 闭包的示例

def closure(x):
    def counter():
        nonlocal x
        x += 1
        return x
    return counter;

counter1 = closure(100);
counter2 = closure(200);

print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))

# result

i from closure 1 101
i from closure 1 102
i from closure 2 201
i from closure 1 103
i from closure 1 104
i from closure 1 105
i from closure 2 202
6赞 Dinesh Sonachalam 12/27/2017 #11
# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

# Defining a closure

# This is an outer function.
def outer_function(message):
    # This is an inner nested function.
    def inner_function():
        print(message)
    return inner_function

# Now lets call the outer function and return value bound to name 'temp'
temp = outer_function("Hello")
# On calling temp, 'message' will be still be remembered although we had finished executing outer_function()
temp()
# Technique by which some data('message') that remembers values in enclosing scopes 
# even if they are not present in memory is called closures

# Output: Hello

关闭需要满足的标准是:

  1. 我们必须有嵌套函数。
  2. 嵌套函数必须引用封闭函数中定义的值。
  3. Enclosing 函数必须返回嵌套函数。

# Example 2
def make_multiplier_of(n): # Outer function
    def multiplier(x): # Inner nested function
        return x * n
    return multiplier
# Multiplier of 3
times3 = make_multiplier_of(3)
# Multiplier of 5
times5 = make_multiplier_of(5)
print(times5(3)) # 15
print(times3(2)) #  6
1赞 Nitish Chauhan 4/18/2018 #12

我们都在 python 中使用过装饰器。它们是展示 python 中什么是闭包函数的好例子。

class Test():
    def decorator(func):
        def wrapper(*args):
            b = args[1] + 5
            return func(b)
        return wrapper

@decorator
def foo(val):
    print val + 2

obj = Test()
obj.foo(5)

这里的最终值是 12

在这里,包装函数能够访问 func 对象,因为包装器是“词法闭包”,它可以访问它的父属性。 这就是为什么它能够访问 func 对象的原因。

1赞 Eunjung Lee 5/12/2018 #13

我想分享我的例子和关于闭包的解释。我做了一个 python 示例,以及两个图来演示堆栈状态。

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n’ * margin_top, a * n, 
            ' ‘ * padding, msg, ' ‘ * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)
…
f('hello')
g(‘good bye!')

此代码的输出如下所示:

*****      hello      #####

      good bye!    ♥♥♥

下面是两个图,分别显示堆栈和附加到函数对象的闭包。

当函数从 Maker 返回时

稍后调用函数时

当通过参数或非局部变量调用函数时,代码需要局部变量绑定,例如 margin_top、填充以及 a、b、n。为了确保函数代码正常工作,应该可以访问很久以前消失的 maker 函数的堆栈帧,该帧与“message”的函数对象一起备份在闭包中。