我该如何理解Python中这个深度绑定的例子?
从编程语言语用学作者Scott,
图3.14 Python中的深度绑定。 右边是运行时栈的概念视图。 在闭包中捕获的参考环境显示为虚线框和箭头。 当通过形式参数P调用B时,存在两个I的实例。 因为P的闭包是在A的初始调用中创建的,所以 B的静态链接(实线箭头)指向该早期调用的框架。 B在其打印语句中使用该调用的I实例,并且输出为1。
问题在于正在运行的程序可能有多个在递归子例程中声明的对象实例。 使用静态范围的语言的闭包在创建闭包时捕获每个对象的当前实例。 当调用闭包的子程序时 ,即使随后通过递归调用创建新的实例,它也会找到这些捕获的实例。
所以基本上,报价试图解释下面的程序(与屏幕截图中的程序相同)打印出1
:
def A(I, P):
def B():
print(I)
# body of A:
if I > 1:
P()
else:
A(2, B)
def C():
pass # do nothing
A(1, C) # main program
我不明白原因是“因为P的闭包是在A的初始调用中创建的,B的静态链接(实线箭头)指向了该早期调用的框架”,并且“当闭包的子例程被调用时,它会找到这些捕获的实例“。 所以我修改了这个例子,然后新的例子输出2
而不是1
:
def A(I, P):
def B():
print(I)
if I > 2:
P()
elif I > 1:
A(3, B)
else:
A(2, B)
def C():
pass
A(1, C)
另一个修改示例打印1
:
def A(I, P):
def B():
print(I)
if I > 2:
P()
elif I > 1:
A(3, P)
else:
A(2, B)
def C():
pass
A(1, C)
那么,我怎么知道哪个闭包很重要?
通常,函数作为参数传递给另一个函数时是否创建了闭包?
谢谢。
对于评论来说这太长了,因此我将其作为答案加入。
我应该说,我从以其他语言学习这些想法的人的角度回答了这个问题:我现在主要是编写Python,但可能我的术语是'错误的'(对于这种术语来说是正确的,但是近期的语言像Python弄错了......)。 特别是我故意滑过一堆特定于Python的细节,并避免处理诸如绑定的可变性和Python 3 nonlocal
攻击等问题。
我也认为这本书对“深度绑定”这个术语的使用很混乱,也许是错误的:请参阅结尾处的注释。 因此,我很大程度上忽视了它。
绑定,范围和程度
有三个重要的概念。
try: ... except: ...
constructs建立的异常类和处理程序之间的绑定)但我只会谈论变量绑定。 范围和范围有几个选项。 对于变量绑定,Python有:
(另一个常见的作用域是动态的 :对于在源代码中可见的任何代码以及从该代码'向下堆栈'的任何代码,动态作用域的绑定是可见的。另一个常见范围是明确的 ,这意味着绑定消失了,控制权离开了建立它的构造,异常处理程序的绑定在Python中具有动态范围和确定范围)。
词汇范围意味着你可以(几乎)通过阅读源代码来看出代码所涉及的是什么。
所以考虑一下:
def outer():
a = 2
def inner(b):
return a + b
return inner(2)
这里有两个绑定: a
绑定在outer
, b
绑定在inner
(实际上,有三个: inner
也绑定到outer
的函数)。 这两个绑定中的每一个都被引用一次:在inner
(并且inner
绑定被引用一次,在outer
)。
而且重要的是,通过阅读代码,你可以告诉引用a
在inner
是:它是在绑定建立outer
。 这就是'词汇'的意思:你(和编译器,如果它足够聪明)可以通过查看源代码来判断存在哪些绑定。
这只是真的。 考虑这个片段:
def maybe(x):
return x + y
有绑定创建一个,在maybe
的,但两个引用: x
是哪个不知道存在有约束力的参考。 但它可能存在:可能有一个顶级绑定的x
,这将使此代码工作。 所以对词汇绑定有一个特别的警告:有一个“顶级”环境,所有定义可以“看到”并且可以包含绑定。 所以,如果上面的片段被放大阅读
x = 4
def maybe(x):
return x + y
然后这段代码很好,因为maybe
可以'看到'顶层环境(实际上,在Python中,这是它定义的模块中的绑定)。
无限的范围和一流的功能
对于上述例子,结果将与确定的或不确定的范围相同。 如果你考虑Python所具有的一流功能,这种情况就不会发生。 它停止了这种情况,因为函数是被调用的对象,可以引用绑定。 考虑这个:
def outer(x):
def inner(y):
return x + y
return inner
有三种绑定这里: outer
结合x
和inner
,和inner
结合y
(和可以看到x
和inner
)。 所以,现在,让add4 = outer(4)
:应该add4(3)
返回(或者等价地,应该返回outer(4)(3)
)? 那么答案是7
。 这种情况是因为x
的绑定存在的时间可以被引用,或者换句话说,只要存在任何inner
实例,它就存在,因为它们引用它。 这是'无限期'的意思。
(如果Python只有一定的范围,那么outer(4)(3)
就会是某种错误,因为它会引用一个不再存在的绑定。只有确定范围的语言在任何情况下都不能有真正的第一类函数有用的方法。)
一些重要的理解是词法范围告诉你哪些绑定是可见的,但是那些可见的绑定的实际实例当然是动态的。 所以,如果你考虑这样的事情:
def adder(n):
return lambda e: e + n
a1 = adder(12)
a2 = adder(15)
那么a1
和a2
引用n
: a1(0)
不同绑定为12
而a2(0)
为15
。 所以通过阅读源代码,你可以知道哪些绑定被捕获,但是你需要运行它来知道它们的哪些实例被捕获 - 换句话说,这些变量的值是什么。
与此比较:
def signaller(initial):
s = [initial]
def setter(new):
s[0] = new
return new
def getter():
return s[0]
return (setter, getter)
(str, gtr) = signaller(0)
现在, str
和gtr
捕获s
的相同绑定,所以str(1)
将导致gtr()
返回1
。
关闭
所以这就是所有知识。 除了有一些人们使用的特殊术语,特别是术语'封闭'。
关闭只是一个函数,它指的是一些自己定义之外的词法绑定。 这种功能被称为“关闭”这些绑定。
我认为这是一个很好的问题,问为什么需要这个词? 您实际需要了解的是范围规则,其他所有内容都遵循这些规则:您为什么需要这个特殊术语? 我认为其原因部分是历史的,部分是实用的:
示例代码
示例代码是
def A(I, P):
def B():
print(I)
# body of A:
if I > 1:
P()
else:
A(2, B)
def C():
pass # do nothing
A(1, C) # main program
所以,当A
被调用时,就有一个本地函数B
,它可以看到I
的绑定(还有P
& B
本身,但它并不涉及这些,所以我们可以忽略它们)。 对A
每次呼叫都会为I
, P
& B
创建新的绑定,并且这些绑定对于每个呼叫都不相同。 这包括递归调用,这是在这里做的混淆你的技巧。
那么, A(1, C)
是做什么的?
I
绑定到1
,将B
绑定到可以看到I
绑定的闭包。 它还将P
绑定到C
的全局(模块)值, C
是一个函数,但没有指向此绑定。 I
是1
),参数为2
和B
的值,这是刚刚创建的闭包。 I
现在绑定到2
, P
从外部调用绑定到闭包。 B
内部调用中绑定它。 没有任何内容涉及这种约束 P
的闭包。 它是在外部调用中创建的闭包,它为I
看到的绑定是可见的绑定,其值为1
。 所以它打印1
,我们就完成了。 您可以通过更改定义来查看发生了什么,以打印一些有用的信息:
from __future__ import print_function # Python 2
def A(I, P):
def B():
print(I)
print("{I} {P} {B}".format(I=I, P=P, B=B))
if I > 1:
P()
else:
A(2, B)
def C():
pass
A(1, C)
这打印(例如):
1 <function C at 0x7f7a03768e60> <function B at 0x7f7a03768d70>
recursing with (2, <function B at 0x7f7a03768d70>)
2 <function B at 0x7f7a03768d70> <function B at 0x7f7a03651a28>
calling <function B at 0x7f7a03768d70>
1
请注意,在内部调用中,有两个函数将自己标识为B
:它们中的一个与在外部调用中创建的函数相同(将被调用),而另一个函数是刚刚创建的闭包,这是从来没有再次引用。
深度绑定
我认为这本书对“深度约束”一词的使用最多是混淆,实际上可能是彻头彻尾的错误:这个词有可能改变了含义,但它当然并不意味着本书认为它的意思。
术语“深度绑定”和“浅层绑定”是指具有动态范围的语言的实现策略。 在具有动态范围的系统中,通过动态搜索调用堆栈直到找到它的绑定为止,查找'空闲'变量引用(不受特定位代码限制的引用)。 因此,在使用动态绑定的语言中,通过查看一些代码可以看出它能够看到什么样的绑定(而编译器也不行),因为它可以看到的绑定取决于当前调用堆栈的样子它运行。
动态绑定非常适用于异常处理程序,但通常对于大多数可变绑定来说都很糟糕。
可怕的一个原因是,一个天真的实现技术使得它本身就很慢,而一个聪明的实现技术需要在具有多个控制线程的系统中工作。
深度绑定是一种天真的实现技术。 在深度绑定中,变量访问按照您的想法工作:系统搜索堆栈,直到找到要查找的名称的绑定,然后使用它。 如果堆叠很深并且绑定距离很远,则速度很慢。
浅绑定是聪明的实现技术。 这样做的效果不是将当前绑定存储在堆栈中,而是将先前的值存储在堆栈中,并将当前值砸成与始终位于相同位置的变量名称关联的槽。 所以现在查找绑定只需要查找名称:没有搜索。 但是创建和销毁绑定可能会比较慢,因为旧值需要隐藏起来。 此外,浅层绑定在多线程控制的情况下并不明显:如果所有线程共享绑定的槽,那么灾难将随之而来。 因此,每个线程需要有自己的插槽,或插槽需要通过线索以及名称进行索引。
我怀疑,对于使用动态范围的情况,例如异常处理程序,系统使用深度绑定,因为在多线程系统中正确运行并且性能并不重要。
这是亨利贝克提出的关于深与浅结合的经典早期论文。
链接地址: http://www.djcxy.com/p/51127.html上一篇: How shall I understand this deep binding example in Python?