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


83

我已经阅读了很多有关闭包的文章,并且我认为我理解它们,但是我希望自己能为我和其他人提供一个简洁而清晰的解释。我正在寻找一个简单的解释,可能有助于我理解在哪里以及为什么要使用它们。

Answers:


95

封闭封闭

对象是带有方法的数据,闭包是带有数据的函数。

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

6
请注意,它nonlocal是在python 3中添加的,python 2.x没有完整的,可读写的闭包(即您可以读取变量的闭包,但不能更改其值)
James Porter

6
@JamesPorter:注意:您可以nonlocal使用可变对象来模拟Python 2中的关键字,例如,L = [0] \n def counter(): L[0] += 1; return L[0]在这种情况下,您不能更改名称(将其绑定到另一个对象), 但是您可以更改名称所引用的可变对象本身至。该列表是必需的,因为整数在Python中是不可变的。
jfs

1
@JFSebastian:对。虽然总是让人觉得自己很肮脏:)
James Porter

46

很简单:一个函数可以从包含的作用域引用变量,可能是在控制流离开该作用域之后。最后一点非常有用:

>>> 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正确闭合。


没有必要做constant = x; 您只需return y + x在嵌套函数中执行操作(或接收名称为的参数constant),它就可以正常工作;闭包捕获的参数与非参数本地变量没有区别。
ShadowRanger

15

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

可以引用不再活动的环境的功能。

我会加

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

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

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


15

老实说,我完全了解闭包,除非我一直不清楚“闭包”到底是什么,以及“闭包”到底是什么。我建议您放弃寻找术语选择背后的任何逻辑。

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

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

bar = foo()
bar()   # print 5

这里的一个关键思想是,即使'x'超出范围并且应该被取消,从foo返回的函数对象仍保留对本地var'x'的钩子。这个钩子指向的是var本身,而不仅仅是var当时的值,因此,在调用bar时,它将输出5,而不是3。

还应该清楚Python 2.x具有有限的闭包:我无法在'bar'内修改'x',因为写'x = bla'会在bar中声明一个本地'x',而不是赋给foo的'x' 。这是Python的assign = 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

7

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

之所以称为闭包,是因为它“封闭”了外部变量(常量),即,它不仅是一个函数,而且是创建该函数的环境的包围。

在以下示例中,在更改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

同样,就目前而言,g()计算x * 2但不返回任何内容。那应该是return x * 2。+1仍然是对“ closure”一词的解释。
布鲁诺·勒弗洛赫

3

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


2

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

实际上,数据模型在其函数__closure__属性描述中对此进行了解释

没有一个单元格或一个包含该函数的自由变量的绑定的单元格元组。只读

为了证明这一点:

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

closure_instance = enclosure('foo')

显然,我们知道现在有一个从变量名指向的函数closure_instance。从表面上看,如果我们使用对象调用它bar,它将打印字符串'foo'以及字符串的任何形式bar

实际上,字符串“ foo”绑定到该函数的实例,我们可以通过访问该cell_contents属性的元组中第一个(也是唯一的)单元格的属性在此处直接读取它__closure__

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

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

“单元”对象用于实现多个作用域引用的变量

并且我们可以演示闭包的用法,注意'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

还有一些更复杂的闭包不适合部分函数示例,我将在时间允许的情况下进一步进行演示。


2
# 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. 封闭函数必须返回嵌套函数。

# 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

这是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

0

对我而言,“封闭”是能够记住其创建环境的功能。此功能允许您在闭包内部使用变量或方法,否则,由于它们不再存在或由于范围而无法使用它们将无法使用。我们来看一下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”变量都不再存在,它也可以工作。都是因为关闭的能力要记住。


0

我们都在python中使用了Decorators。它们是展示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对象。


0

我想分享我的例子和关于闭包的解释。我制作了一个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!    ♥♥♥

这是两个图,显示了堆栈和附加到函数对象的闭包。

从制造商返回功能时

稍后调用该函数时

通过参数或非局部变量调用该函数时,代码需要局部变量绑定,例如margin_top,padding以及a,b,n。为了确保功能代码正常工作,应该可以访问很早以前就消失的maker函数的堆栈框架,该框架在我们可以与“消息的函数”对象一起找到的闭包中得到备份。


-2

我见过的关于闭包的最好解释是解释该机制。它是这样的:

可以将程序堆栈想象成一棵简并树,其中每个节点只有一个孩子,而单个叶节点就是当前执行过程的上下文。

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

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


那不是对闭包的解释。
Jules

您是在描述连续而不是闭包。
马修·奥莱尼克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.