Python lambda函数访问外部变量

Python lambda函数访问外部变量,python,scope,Python,Scope,我想玩匿名函数,所以我决定做一个简单的素数查找器。这是: tests=[] end=int(1e2) i=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/ resulti

我想玩匿名函数,所以我决定做一个简单的素数查找器。这是:

tests=[]
end=int(1e2)
i=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时“捕获”
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()

问题是
测试中的每个函数都引用了变量
i

更常见的情况是,在函数中执行此操作,在这种情况下,定义范围变量
i
有一个局部变量,该变量存储在闭包中,如中所述

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


传统的解决方法(适用于闭包和全局函数)被亲切地称为“默认值hack”,尽管它不是真正的hack。(请参阅。)Ryan Haining的回答解释了如何做到这一点:

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


另一种方法是创建一个函数创建函数,并将
i
的值作为参数传递给该函数创建函数,这在使用JavaScript等语言时可能会更为常见,如user2864740的回答:

(lambda i: lambda x: x%i)(i)
这避免了用一个额外的参数“污染”函数的签名(有人可能会意外地将参数传递给该参数),但代价是创建和调用一个函数没有好的理由


解决这个问题的第三种方法是使用
partial
。如果您只想部分应用一个函数,那么使用
partial
而不是将包装函数定义为
lambda
会更简洁

不幸的是,在本例中,该函数隐藏在运算符中,而公开它的函数
operator.mod
不接受关键字参数,因此无法有效地对其第二个操作数进行局部运算。所以,在这种情况下,这是一个糟糕的解决方案。如果您真的愿意,您可以编写一个性能更好的包装器,并且
部分
可以:

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

partial(operator.mod, b=i)

在这种情况下,我认为你最好使用其他解决方案;在适当的情况下,请记住这一点。

这是有效的,因为创建函数时会计算默认参数,而调用函数时会进行变量查找。太棒了“捕获”保存了我的一天。也可以查看比官方文档中更好的解释。但简短的版本是,这些测试函数中的每一个实际上都是围绕同一变量
i
的闭包,并且
i
不断改变值。(这不太准确,因为全局变量实际上不需要存储在闭包中,但效果是一样的。)我在您提供的链接中找不到与此相关的任何内容。@erm3nda答案中至少有两个链接,我不知道您的评论中“那”指的是什么。抱歉。关于@erm3nda Ah的默认值hack,这似乎是常见问题解答的编程章节,而不是设计章节。我不知道这在过去的4年里是否发生了变化,或者你是否是第一个真正关注这个链接的人,但是不管怎样,谢谢你捕捉到它,我会编辑它。谢谢