如何在Python中将参数绑定到函数?

82

如何将参数绑定到Python函数中,以便稍后在不带参数(或带有较少附加参数)的情况下调用它?

例如:

def add(x, y):
    return x + y

add_5 = magic_function(add, 5)
assert add_5(3) == 8

我需要在这里使用什么神奇函数


在使用框架和库时,人们经常会在试图将参数传递给回调函数时意外地立即调用一个函数,例如 on_event(action(foo))。解决方法是使用本文中描述的技术之一,将 foo 绑定为 action 的参数。例如,请参见 如何在Tkinter中将参数传递给按钮命令?在Python中使用字典作为开关语句

然而,有些API允许您单独传递要绑定的参数,并会为您执行绑定。值得注意的是,标准库中的线程API就是这样工作的。请参见 在调用Thread.start之前线程开始运行。如果您正在尝试设置自己的API,请参见 如何编写一个简单的回调函数?

显式绑定参数也是避免使用闭包时晚绑定问题的一种方法。这个问题是指,例如,在for循环或列表推导式中的lambda会产生计算相同结果的单独函数。请参见lambda函数闭包捕获了什么?在循环(或推导式)中创建函数(或lambda)

7个回答

93

functools.partial 返回一个可调用对象,其中包装了一些或全部参数已被冻结的函数。

import sys
import functools

print_hello = functools.partial(sys.stdout.write, "Hello world\n")

print_hello()
Hello world

以上用法等同于以下lambda表达式。

print_hello = lambda *a, **kw: sys.stdout.write("Hello world\n", *a, **kw)

1
对于Python2.6版本,你可以使用"from future import print_function"。 - Xavier Combelle

77

使用 functools.partial

>>> from functools import partial
>>> def f(a, b):
...     return a+b
... 
>>> p = partial(f, 1, 2)
>>> p()
3
>>> p2 = partial(f, 1)
>>> p2(7)
8

14
这个例子比被采纳的答案更好,因为它展示了部分绑定。 - AShelly
1
虽然如果函数f本身不对其两个参数进行对称处理,以便明确哪个参数是在部分绑定中绑定的(我假设是第一个),那将会更好 - 就像def f(a, b): return a + 2*b一样。 - Dan Nissenbaum

14

如果 functools.partial 不可用,那么可以很容易地模拟出来:

>>> make_printer = lambda s: lambda: sys.stdout.write("%s\n" % s)
>>> import sys
>>> print_hello = make_printer("hello")
>>> print_hello()
hello

或者

def partial(func, *args, **kwargs):
    def f(*args_rest, **kwargs_rest):
        kw = kwargs.copy()
        kw.update(kwargs_rest)
        return func(*(args + args_rest), **kw) 
    return f

def f(a, b):
    return a + b

p = partial(f, 1, 2)
print p() # -> 3

p2 = partial(f, 1)
print p2(7) # -> 8

d = dict(a=2, b=3)
p3 = partial(f, **d)
print p3(), p3(a=3), p3() # -> 5 6 5

1
作为历史记录:functools标准库是在2.5中引入的,该版本于2006年首次发布。此答案是在2.5.3和2.5.4更新之前不久编写的。相当数量的用户可能仍在使用2.4,甚至是2.3。 - Karl Knechtel

13

lambda允许您创建一个新的未命名函数,参数更少,并调用该函数:

>>> def foobar(x, y, z):
...     print(f'{x}, {y}, {z}')
... 
>>> foobar(1, 2, 3)  # call normal function
1, 2, 3
>>> bind = lambda x: foobar(x, 10, 20)  # bind 10 and 20 to foobar
>>> bind(1)
1, 10, 20
>>> bind = lambda: foobar(1, 2, 3)  # bind all elements
>>> bind()
1, 2, 3

您也可以使用functools.partial。如果您计划在函数调用中使用命名参数绑定,那么这也适用:

>>> from functools import partial
>>> barfoo = partial(foobar, x=10)
>>> barfoo(y=5, z=6)
10, 5, 6

请注意,如果你从左侧绑定参数,则需要按名称调用参数。如果你从右侧绑定参数,则会按预期工作。
>>> barfoo(5, 6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foobar() got multiple values for argument 'x'
>>> f = partial(foobar, z=20)
>>> f(1, 1)
1, 1, 20

1
这应该是被接受的答案。这是将参数绑定到可调用对象的Pythonic方式。 - thehouse
避免使用命名的lambda表达式,改用def - wjandrea

7
这也可以起作用:
def curry(func, *args):
    def curried(*innerargs):
       return func(*(args+innerargs))
    curried.__name__ = "%s(%s, ...)" % (func.__name__, ", ".join(map(str, args)))
    return curried

>>> w=curry(sys.stdout.write, "Hey there")
>>> w()
Hey there

2
这不是咖喱。柯里化适用于具有多个参数的函数。柯里化的工作方式如下:给定 g = curry(f),我们有 f(x1,x2,...) = g(x1)(x2)... - Gavin Haynes
@GavinHaynes 您是正确的 - “柯里化”应该意味着将f转换为g,但它通常用于表示部分应用程序(即“绑定”)。有关实际柯里化,请参见Python中的柯里化装饰器。有关相关的学究气息,请参见柯里化和部分应用程序之间的区别是什么? - Karl Knechtel

1

在Python中,可以这样定义函数对象。它们是可调用对象,而“绑定”仅设置参数值。

class SomeFunctor( object ):
    def __init__( self, arg1, arg2=None ):
        self.arg1= arg1
        self.arg2= arg2
    def __call___( self, arg1=None, arg2=None ):
        a1= arg1 or self.arg1
        a2= arg2 or self.arg2
        # do something
        return

你可以做像这样的事情

x= SomeFunctor( 3.456 )
x( arg2=123 )

y= SomeFunctor( 3.456, 123 )
y()

1
有趣的模式,尽管在微不足道的函数中有很多样板文件。 - Dustin Getz
1
根据您的实际用例,您可以将其简化为相当简单的内容。原始问题没有提供有关绑定发生的指导。没有具体细节,需要涵盖的基础太多了。 - S.Lott
使用这样的类被一些人不赞成,并有明确的理由 - Karl Knechtel

1
这个问题通常询问绑定参数,但所有答案都是关于函数的。如果你想知道,partial也适用于类构造函数(即使用类而不是函数作为第一个参数),这对于工厂类很有用。你可以按如下方式进行操作:
from functools import partial

class Animal(object):
    def __init__(self, weight, num_legs):
        self.weight = weight
        self.num_legs = num_legs
        
animal_class = partial(Animal, weight=12)
snake = animal_class(num_legs = 0)
print(snake.weight) # prints 12

这似乎更像是现有答案的附注。类是可调用的,因此在Python的鸭子类型哲学下,能够与函数一起使用的工具也能够与类实例化一起使用,这并不令人惊讶。 - Karl Knechtel

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