词汇闭包如何工作?[重复]

2024-12-17 08:30:00
admin
原创
117
摘要:问题描述:当我调查 Javascript 代码中词法闭包的问题时,我在 Python 中遇到了这个问题:flist = [] for i in xrange(3): def func(x): return x * i flist.append(func) for f in flist: ...

问题描述:

当我调查 Javascript 代码中词法闭包的问题时,我在 Python 中遇到了这个问题:

flist = []

for i in xrange(3):
    def func(x): return x * i
    flist.append(func)

for f in flist:
    print f(2)

请注意,此示例刻意避免了lambda。它打印“4 4 4”,这令人惊讶。我期望的是“0 2 4”。

这个等效的 Perl 代码可以正确完成此操作:

my @flist = ();

foreach my $i (0 .. 2)
{
    push(@flist, sub {$i * $_[0]});
}

foreach my $f (@flist)
{
    print $f->(2), "
";
}

打印“0 2 4”。

你能解释一下其中的区别吗?


更新:

问题在于i全球化。以下代码显示了相同的行为:

flist = []

def outer():
    for i in xrange(3):
        def inner(x): return x * i
        flist.append(inner)

outer()
#~ print i   # commented because it causes an error

for f in flist:
    print f(2)

如注释行所示,i此时是未知的。但它仍然打印“4 4 4”。


解决方案 1:

Python 的行为实际上与定义一致。创建了三个独立的函数,但它们每个都有定义它们的环境的闭包- 在本例中是全局环境(如果循环放在另一个函数内,则是外部函数的环境)。然而,这正是问题所在 - 在这个环境中,i 被修改了,而所有闭包都引用同一个 i

这是我能想到的最佳解决方案 - 创建一个函数创建器并调用。这将强制每个创建的函数使用不同的环境,每个函数中的i 都不同。

flist = []

for i in xrange(3):
    def funcC(j):
        def func(x): return x * j
        return func
    flist.append(funcC(i))

for f in flist:
    print f(2)

当你混合副作用和函数式编程时就会发生这种情况。

解决方案 2:

循环中定义的函数不断访问同一个变量i,而该变量的值会发生变化。在循环结束时,所有函数都指向同一个变量,该变量保存着循环中的最后一个值:效果如示例中所报告的那样。

为了评估i和使用其值,一种常见的模式是将其设置为参数默认值:在执行语句时评估参数默认值def,从而冻结循环变量的值。

以下工作符合预期:

flist = []

for i in xrange(3):
    def func(x, i=i): # the *value* of i is copied in func() environment
        return x * i
    flist.append(func)

for f in flist:
    print f(2)

解决方案 3:

以下是使用该库执行此操作的方法functools(我不确定提出问题时该库是否可用)。

from functools import partial

flist = []

def func(i, x): return x * i

for i in range(3):
    flist.append(partial(func, i))

for f in flist:
    print(f(2))

正如预期,输出 0 2 4。

解决方案 4:

看看这个:

for f in flist:
    print f.func_closure


(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)

这意味着它们都指向同一个 i 变量实例,循环结束后其值将为 2。

一个可读的解决方案:

for i in xrange(3):
        def ffunc(i):
            def func(x): return x * i
            return func
        flist.append(ffunc(i))

解决方案 5:

发生的事情是,变量 i 被捕获,函数返回它在调用时绑定的值。在函数式语言中,这种情况永远不会发生,因为 i 不会被重新绑定。然而,对于 python 以及您在 lisp 中看到的,情况不再如此。

您的 Scheme 示例的不同之处在于 do 循环的语义。Scheme 实际上是在每次循环中创建一个新的 i 变量,而不是像其他语言一样重复使用现有的 i 绑定。如果您使用在循环外部创建的其他变量并对其进行变异,您将在 Scheme 中看到相同的行为。尝试用以下内容替换您的循环:

(let ((ii 1)) (
  (do ((i 1 (+ 1 i)))
      ((>= i 4))
    (set! flist 
      (cons (lambda (x) (* ii x)) flist))
    (set! ii i))
))

请看这里以进一步讨论此问题。

[编辑] 可能更好的描述方法是将 do 循环视为执行以下步骤的宏:

  1. 定义一个采用单个参数 (i) 的 lambda,其主体由循环体定义,

  2. 立即调用该 lambda 表达式,并使用适当的 i 值作为其参数。

即相当于下面的python:

flist = []

def loop_body(i):      # extract body of the for loop to function
    def func(x): return x*i
    flist.append(func)

map(loop_body, xrange(3))  # for i in xrange(3): body

i 不再是来自父作用域的变量,而是其自身作用域中的一个全新变量(即 lambda 的参数),因此您可以观察到这种行为。Python 没有这种隐式的新作用域,因此 for 循环的主体只是共享 i 变量。

解决方案 6:

问题在于所有本地函数都绑定到同一个环境,因此也绑定到同一个i变量。解决方案(解决方法)是为每个函数(或 lambda)创建单独的环境(堆栈框架):

t = [ (lambda x: lambda y : x*y)(x) for x in range(5)]

>>> t[1](2)
2
>>> t[2](2)
4

解决方案 7:

我仍然不完全明白为什么在某些语言中这是这样工作的,而在另一些语言中又是那样工作的。在 Common Lisp 中它就像 Python 一样:

(defvar *flist* '())

(dotimes (i 3 t)
  (setf *flist* 
    (cons (lambda (x) (* x i)) *flist*)))

(dolist (f *flist*)  
  (format t "~a~%" (funcall f 2)))

打印“6 6 6”(请注意,这里的列表是从 1 到 3,并且是反向构建的)。而在 Scheme 中,它的工作方式与在 Perl 中类似:

(define flist '())

(do ((i 1 (+ 1 i)))
    ((>= i 4))
  (set! flist 
    (cons (lambda (x) (* i x)) flist)))

(map 
  (lambda (f)
    (printf "~a~%" (f 2)))
  flist)

打印“6 4 2”

正如我已经提到的,JavaScript 属于 Python/CL 阵营。这似乎是一个实现决策,不同的语言以不同的方式处理。我很想了解这个决策到底是什么。

解决方案 8:

该变量是全局变量,每次调用i该函数时其值都是 2 。f

我倾向于按照如下方式实现您所追求的行为:

>>> class f:
...  def __init__(self, multiplier): self.multiplier = multiplier
...  def __call__(self, multiplicand): return self.multiplier*multiplicand
... 
>>> flist = [f(i) for i in range(3)]
>>> [g(2) for g in flist]
[0, 2, 4]

对您的更新的回复:造成这种行为的原因不是i 本身的全局性,而是因为它是来自封闭范围的变量,在调用 f 时具有固定值。在您的第二个示例中, 的值i取自函数的范围kkk,当您在 上调用函数时,没有任何变化flist

解决方案 9:

这种行为背后的原因已经得到解释,并且已经发布了多种解决方案,但我认为这是最符合 Python 风格的(记住,Python 中的一切都是对象!):

flist = []

for i in xrange(3):
    def func(x): return x * func.i
    func.i=i
    flist.append(func)

for f in flist:
    print f(2)

Claudiu 的答案非常好,使用了一个函数生成器,但是 piro 的答案老实说是一种黑客行为,因为它将 i 变成了一个具有默认值的“隐藏”参数(它可以正常工作,但它不是“pythonic”的)。

解决方案 10:

我不喜欢上面的解决方案wrappers在循环中创建的方式。注意:python 3.xx

flist = []

def func(i):
    return lambda x: x * i

for i in range(3):
    flist.append(func(i))

for f in flist:
    print f(2)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用