你能解释一下闭包(因为它们与 Python 相关)吗?
-
08-06-2019 - |
题
我已经阅读了很多有关闭包的内容,并且我认为我理解它们,但在不给我自己和其他人带来困扰的情况下,我希望有人能够尽可能简洁明了地解释闭包。我正在寻找一个简单的解释,可以帮助我理解在哪里以及为什么要使用它们。
解决方案
对象是带有附加方法的数据,关闭是随附数据的功能。
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
其他提示
这很简单:从包含范围引用变量的函数,可能在控制流离开该范围之后。最后一点非常有用:
>>> 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 成为正确的闭包。
我喜欢 这个粗略、简洁的定义:
可以引用不再活动的环境的函数。
我想补充一下
闭包允许您将变量绑定到函数中 而不将它们作为参数传递.
接受参数的装饰器是闭包的常见用途。闭包是此类“函数工厂”的常见实现机制。我经常选择在 策略模式 当策略在运行时被数据修改时。
在允许匿名块定义的语言中(例如 Ruby、C#),闭包可用于实现(相当于)新颖的控制结构。缺乏匿名区块是其中之一 Python 中闭包的局限性.
老实说,我非常了解闭包,只是我一直不清楚“闭包”到底是什么以及它的“闭包”是什么。我建议您放弃寻找术语选择背后的任何逻辑。
无论如何,这是我的解释:
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 的赋值=声明的副作用。为了解决这个问题,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
我从来没有听说过在解释什么是闭包时在相同的上下文中使用事务,并且这里实际上没有任何事务语义。
它被称为闭包,因为它“封闭”了外部变量(常量)——也就是说,它不仅仅是一个函数,而且是创建该函数的环境的封闭体。
在下面的示例中,在更改 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
这是闭包的典型用例 - GUI 元素的回调(这将是子类化按钮类的替代方法)。例如,您可以构造一个函数,该函数将在按下按钮时调用,并“关闭”父作用域中处理单击所需的相关变量。通过这种方式,您可以从同一个初始化函数连接相当复杂的接口,将所有依赖项构建到闭包中。
在 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
还有更复杂的闭包不适合部分函数示例,我将在时间允许的情况下进一步演示它们。
这是一个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
# 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
闭包要满足的标准是:
- 我们必须有嵌套函数。
- 嵌套函数必须引用封闭函数中定义的值。
- 封闭函数必须返回嵌套函数。
# 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
对我来说,“闭包”是能够记住它们创建的环境的函数。此功能允许您在闭包中使用变量或方法,换句话说,您将无法使用它们,因为它们不再存在,或者由于范围而超出了范围。让我们看看 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”变量不再存在,它仍然有效。一切都是因为封闭能力的记忆。
我们都用过 装饰器 在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 对象。
我想分享我的例子和关于闭包的解释。我做了一个 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函数的堆栈框架应该是可访问的,它在我们可以与“消息”函数对象一起找到的闭包中得到备份。
我见过的关于闭包的最好解释就是解释其机制。事情是这样的:
将您的程序堆栈想象为一棵退化树,其中每个节点只有一个子节点,并且单个叶节点是当前正在执行的过程的上下文。
现在放宽每个节点只能有一个子节点的约束。
如果这样做,您可以拥有一个构造(“yield”),它可以从过程返回而不丢弃本地上下文(即当您返回时,它不会将其从堆栈中弹出)。下次调用该过程时,该调用将拾取旧的堆栈(树)帧并从中断处继续执行。