当前位置:  开发笔记 > 编程语言 > 正文

词法闭包如何工作?

如何解决《词法闭包如何工作?》经验,为你挑选了5个好方法。

当我在调查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), "\n";
}

打印"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> piro..:

循环中定义的函数在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)


这是一个巧妙的解决方案,这让它变得非常糟糕.
s /在编译时/执行`def`语句时/

2> Claudiu..:

Python实际上是按照定义的行为.创建了三个单独的函数,但它们各自具有它们所定义的环境闭包 - 在这种情况下,是全局环境(如果循环放在另一个函数内,则为外部函数的环境).这正是问题所在 - 在这种环境中,我是变异的,闭包都是指同一个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)

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


.这将为每个创建的函数强制使用
这不是行为不端.它的行为完全符合定义.
怎么样只是这样定义:`def inner(x,i = i):return x*i`
IMO piro有更好的解决方案http://stackoverflow.com/questions/233673/lexical-closures-in-python#235764
您的解决方案也是Javascript中使用的解决方案.
为清晰起见,我可能会将最里面的'i'改为'j'.

3> Luca Inverni..:

以下是使用functools库(我不确定在提出问题时可用)的方法.

from functools import partial

flist = []

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

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

for f in flist:
    print f(2)

按预期输出0 2 4.


要将这种技术应用于方法,您还可以从python 3.4开始使用`functools.partialmethod()`。

4> Null303..:

看这个:

for f in flist:
    print f.func_closure


(,)
(,)
(,)

这意味着它们都指向相同的i变量实例,一旦循环结束,它将具有值2.

可读解决方案:

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


询问为什么有些缺陷是假设它不是一个缺陷.

5> Brian..:

发生的事情是捕获变量i,并且函数返回它在调用时绑定的值.在函数式语言中,这种情况永远不会出现,因为我不会反弹.但是对于python,以及你在lisp中看到的情况,这已不再适用.

与您的方案示例的不同之处在于do循环的语义.Scheme每次循环都有效地创建一个新的i变量,而不是像其他语言那样重用现有的i绑定.如果您使用在循环外部创建的不同变量并对其进行变异,您将在方案中看到相同的行为.尝试用以下代码替换循环:

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

看看这里有关于此的进一步讨论.

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

    使用单个参数(i)定义一个lambda,使用由循环体定义的主体,

    立即调用该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变量.

推荐阅读
我我檬檬我我186
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有