lambda函数访问外部变量

Posted

技术标签:

【中文标题】lambda函数访问外部变量【英文标题】:lambda function accessing outside variable [duplicate] 【发布时间】:2014-01-29 23:55:47 【问题描述】:

我想尝试使用匿名函数,所以我决定制作一个简单的素数查找器。这里是:

tests = []
end = int(1e2)
i = 3
while i <= end:
    a = map(lambda f:f(i),tests)
    if True not in a:
        tests.append(lambda x:x%i==0)
        print i
    print tests
    print "Test: "+str(i)
    print str(a)
    i+=2

然而,我发现lambda x:x%i==0 中的i 每次都被访问,而我希望它是一个文字数字。我怎样才能让它变成lambda x:x%3==0

【问题讨论】:

另见These Nasty Closures 以获得比官方文档中更好的解释。但简短的版本是这些测试函数中的每一个实际上都是围绕同一个变量i 的闭包,并且i 不断变化的值。 (这不太准确,因为全局变量实际上并不需要存储在闭包中,但效果是一样的。) 【参考方案1】:

您可以在创建 lambda 时“捕获”i

lambda x, i=i: x%i==0

这会将 lambda 上下文中的 i 设置为等于创建时的 i。如果你愿意,你也可以说 lambda x, n=i: x%n==0。它不完全是捕获,但它可以为您提供所需的内容。


这是一个类似于以下定义函数的查找问题:

i = "original"

def print_i1():
    print(i) # prints "changed" when called below

def print_i2(s=i): # default set at function creation, not call
    print(s) # prints "original" when called below


i = "changed"
print_i1()
print_i2()

【讨论】:

这是可行的,因为默认参数是在函数创建时评估的,而变量查找是在函数调用时完成的。 太棒了。 “捕获”节省了我的时间。【参考方案2】:

问题在于tests 中的每个函数都引用了变量i

更常见的是,您在函数中执行此操作,在这种情况下,您有一个局部定义范围变量i,它存储在闭包中,正如These Nasty Closures 中所解释的那样。

但在这里,它更简单:i 是一个全局变量,所以没有闭包。这些函数被编译为在运行时将i 作为全局变量查找。由于i 已更改,因此函数在运行时将看到更改的值。就这么简单。


解决这个问题的传统方法(适用于闭包和全局变量)被亲切地称为“默认值 hack”,尽管它并不是真正的 hack。 (见the explanation in the FAQ。)Ryan Haining 的回答解释了如何做到这一点:

lambda x, i=i: x%i==0

这将创建一个名为i 的参数,其默认值等于创建函数时i 的值。然后,在函数内部,当您访问参数i 时,您将获得该值。


另一种解决方法是创建一个函数创建函数,并将 i 的值作为参数传递给该函数创建函数,如 user2864740 的回答:

(lambda i: lambda x: x%i)(i)

这避免了使用额外参数(有人可能不小心将参数传递给)“污染”函数的签名,但代价是无缘无故地创建和调用函数。


第三种方法是使用partial。如果您想要做的只是部分应用函数,使用 partial 而不是将包装函数定义为 lambda 会更简洁。

不幸的是,在这种情况下,该函数隐藏在一个运算符中,并且公开它的函数operator.mod 不接受关键字参数,因此您不能有效地对它的第二个操作数进行偏分。因此,在这种情况下,这是一个糟糕的解决方案。如果你真的想要,你可以写一个表现更好的包装器和partial

def opmod(a, b):
    return a % b

partial(operator.mod, b=i)

在这种情况下,我认为您最好使用其他解决方案;把这个放在你的脑海里,以备不时之需。

【讨论】:

我在您提供的链接上找不到与此相关的任何内容。 @erm3nda 答案中至少有两个链接,我不知道您评论中的“那个”指的是什么。 对不起。关于docs.python.org/3/faq/…的默认值破解@ @erm3nda 啊,这似乎是在常见问题解答的编程章节,而不是设计章节。我不知道这在过去 4 年中是否发生了变化,或者您是否是第一个真正关注该链接的人,但无论哪种方式,感谢您抓住它,我会进行编辑。 docs.python.org/3/faq/… 谢谢。【参考方案3】:

创建一个返回 lambda 的新函数。然后调用它,传入i 作为参数。这将创建一个新的绑定范围。

def make_test (i):
   # this i refers to the parameter (which evaluates to the /value/ passed)
   return lambda x: x%i==0

# ..
# the /value/ resulting from evaluating the variable is passed
tests.append(make_test(i))

【讨论】:

以上是关于lambda函数访问外部变量的主要内容,如果未能解决你的问题,请参考以下文章

允许 lambda 函数访问 S3 存储桶但阻止外部 IP

lambda表达式

关于 C# 闭包

在lambda函数中访问变量名

在lambda函数中访问周围变量时出错

C# 闭包和对外部变量的访问