Python中引用局部变量的匿名函数

7

我可以如何在Python中定义匿名函数,使其行为取决于定义时的本地变量值,并且接受参数?

示例:

def callback(val1, val2):
   print "{0} {1}".format(val1, val2)

i = 0
f0 = lambda x: callback(i, x)
i = 1
f1 = lambda x: callback(i, x)

f0(8) # prints "1, 8: but I'd like "0, 8" (value of 'i' when f0 was defined)
f1(8) # prints "1, 8"

不用将我的回调函数包装在自己的类中,是否可以实现这样的功能?

你正在定义相同的Lambda函数两次。 - Joel Cornett
@JoelCornett:是的,他确实是这样做的,但这是他承认的未成功尝试闭包,因为他总是引用那个全局的i - jdi
@jdi:噢,我明白了。是的,我同意你的答案,会用partial - Joel Cornett
4个回答

8

Closures in python using functools.partial

from functools import partial

i = 0
f0 = partial(callback, i)
i = 1
f1 = partial(callback, i)

f0()
# 0
f1()
# 1

partial类似于lambda表达式,但它会将该时刻的值包装到参数中,而不是在调用时计算。

仅包装部分参数

是的,partial可以让你包装任意数量的参数,剩余的args和kwargs可以传递给生成的partial对象,使其像调用原始包装函数一样运行...

def callback(val1, val2):
   print "{0} {1}".format(val1, val2)

i = 0
x = 8
f0 = partial(callback, i)
f0(x)
# 0 8

本质上,您已经将 callback(val1, val2) 包装成了 callback(val2),其中 val1 已作为闭包被包含在内。

使用lambda实现类似效果的示例

如果您真的想看看如何使用lambda闭包来完成这个过程,您会发现它变得很丑陋,而部分应用则更受欢迎...

f0 = (lambda val1: lambda val2: callback(val1, val2))(i)

你需要将作用域变量包装在外部函数作用域中,然后在内部lambda函数中引用该作用域。但是这样做比较麻烦。

异常的追踪:partial vs lambda vs nested functions

随着其他答案的涌现,我想再提出一个使用partial而不是lambda或内/外函数闭包的原因。请记住,我指的是函数闭包。 functools.partial可以修复当包装函数引发异常时,您将得到的回溯信息...

考虑此版本,它将引发除零错误:

def callback(val1, val2):
    return val1 / val2

常规外部/内部闭包

def wrapper(fn, val1):
    def wrapped(val2):
            return fn(val1, val2)
    return wrapped

f0 = wrapper(callback, i)
f0(0)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in wrapped
  File "<stdin>", line 2, in callback
ZeroDivisionError: integer division or modulo by zero

lambda闭包

f0 = (lambda val1: lambda val2: callback(val1, val2))(i)
f0(0)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 2, in callback
ZeroDivisionError: integer division or modulo by zero

现在来谈 functools.partial

f0 = partial(callback, i)
f0(0)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in callback
ZeroDivisionError: integer division or modulo by zero

partial 能否生成一个接受参数的函数?(请参见编辑) - ajwood

1
您可以通过 functools.partial 来实现这一点:
f0 = partial(callback, i)

0
你可以创建一个生成函数的函数。另一个选项是使用其他人提到的partial
def repeater(s):
  def anon():
      print s
  return anon

greet = repeater("Hello, World")
greet()

0

虽然在这种情况下使用functools.partial是更好的解决方案,但您也可以创建显式闭包:

def callback_generator(val):
    def callback():
        return val
    return callback
i = 0
f0 = callback1(i)
i = 1
f1 = callback1(i)

这也可以用lambda表达式来实现,如下面的注释所示。


lambda 是 Python 中的匿名函数。你可以创建多个匿名函数,例如:f = lambda x: (lambda y: y+1)(x+1)。在 Python 中使用 lambda 实现闭包的方式是:f = (lambda x: lambda: x)(i) - jdi
请查看我的答案更新,解释如何完全使用匿名 lambda 函数来实现它。 - jdi
哦,我从没想过嵌套lambda。不过为什么要踩我呢?如果我没提起闭包,你会讲的更详细吗?当然,这确实使得这个答案更有用。 - forivall
这是因为你的回答中有错误信息。但是你已经纠正了它。所以现在一切都好。 - jdi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接