在Python中,如何将一个函数(回调)作为另一个函数的参数使用?

137
假设我有一些代码,如下所示:
def myfunc(anotherfunc, extraArgs):
    # somehow call `anotherfunc` here, passing it the `extraArgs`
    pass

我想将另一个现有的函数作为anotherfunc参数传递,并将参数列表或元组作为extraArgs,并让myfunc使用这些参数调用传入的函数。

这种做法可行吗?我该如何实现?


相关链接:https://dev59.com/gqfja4cB1Zd3GeqPpxHH#47634215 - alseether
11个回答

154

是的,这是可能的。myfunc可以像这样调用传入的函数:

def myfunc(anotherfunc, extraArgs):
    anotherfunc(*extraArgs)

这里是一个完整的示例:

>>> def x(a, b):
...     print('a:', a, 'b:', b)
... 
>>> def y(z, t):
...     z(*t)
... 
>>> y(x, ('hello', 'manuel'))
a: hello b: manuel

1
extraArgs也可以是一个函数吗?如果可以,那么如何调用它? - Aysennoussi
1
@sekai 是的,extraArgs 也可以是一个函数。 - Manuel Salvadores
4
这个记录在哪里? - 4dc0
2
@4dc0 我认为这并没有直接说明,它只是Python数据模型如何工作的自然结果。可以绑定到名称的所有内容都是对象;对象可以传递给函数;函数可以绑定到名称(这就是使用def时发生的情况);因此,函数可以传递给函数。也就是说,您可能会对https://docs.python.org/3.11/howto/functional.html 感兴趣,其中展示了基本技术,利用了这个现实,并展示了一些标准库好东西 旨在利用它。 - Karl Knechtel

34
这是另一种使用*args(以及可选的**kwargs)的方法:
def a(x, y):
    print(x, y)

def b(other, function, *args, **kwargs):
    function(*args, **kwargs)
    print(other)

b('world', a, 'hello', 'dude')

输出

hello dude
world

请注意,function*args**kwargs必须以这个顺序出现,并且必须是调用function的函数(b)中的最后一个参数。

4
有关 *args 和 **kwargs 的更多信息可以在此处找到:https://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/ 。 - Pipo

19
Python中的函数是一等公民。然而,函数定义应该稍有不同
def myfunc(anotherfunc, extraArgs, extraKwArgs):
    return anotherfunc(*extraArgs, **extraKwArgs)

5

当然,这就是为什么Python实现了以下方法,其中第一个参数是函数:

  • map(function, iterable, ...) - 将函数应用于可迭代对象的每个项目,并返回结果列表。
  • filter(function, iterable) - 从可迭代对象中构建一个列表,其中包含函数返回true的元素。
  • reduce(function, iterable [,initializer]) - 将两个参数的函数累积地应用于iterable的项目,从左到右,以将可迭代对象减少为单个值。
  • lambdas

参见:https://docs.python.org/3.11/howto/functional.html。内置函数还包括`sort`、`min`和`max`,它们接受一个`key`关键字参数,该参数应为可调用对象。 - Karl Knechtel
我并不认为Lambda属于同一类别。它们不是内置函数(也不是“方法”),而是语法;Lambda的第一个参数绝对不需要接受一个函数。实际上,Lambda根本不需要任何参数。 - Karl Knechtel

3

一个函数可以作为另一个函数的参数,也可以返回另一个函数。

以下是一个例子:

def out_func(a):
    def in_func(b):
        print(a + b + b + 3)
    return in_func
 
obj = out_func(1)
print(obj(5)) # outputs 14

1

是的,这是可能的。像使用其他函数一样使用该函数:anotherfunc(*extraArgs)


1

可以通过将一个函数的调用作为另一个函数的参数来同时调用两个或多个函数:

def anotherfunc(inputarg1, inputarg2):
    pass
def myfunc(func = anotherfunc):
    print(func)

myfunc(anotherfunc(inputarg1, inputarg2))

这将导致myfunc打印anotherfunc调用的返回值(即None)。


这与实际问题完全无关。 - Karl Knechtel

1

装饰器在Python中非常强大,因为它们允许程序员将一个函数作为参数传递,并且还可以在另一个函数内定义一个函数。

def decorator(func):
    def insideFunction():
        print("This is inside function before execution")
        func()
    return insideFunction

def func():
    print("I am argument function")

func_obj = decorator(func) 
func_obj()

输出:

This is inside function before execution
I am argument function

参见:https://dev59.com/0XRB5IYBdhLWcg3wCjjO#1594484 - Karl Knechtel

1

概述

是的,这是可能的。

在问题的示例中,myfuncanotherfunc 参数是 回调 的一个例子,因此 myfunc高阶函数(以下简称 HOF)的一个例子。

编写 HOF 并为其提供回调的一个简单示例可能如下所示:

def higher_order(a_callback):
    print("I will call:", a_callback)
    a_callback()

def my_callback():
    print("my_callback was called")

higher_order(my_callback)

请注意,示例代码中传递的是my_callback - 只需使用函数名称,不要在其后加上括号。 如果错误地编写了 higher_order(my_callback()) 将意味着首先调用 my_callback ,然后将返回值( 这里将是 None )传递给 higher_order 。这将导致 TypeError ,因为 None 不可调用

在函数本身中,为了接受另一个函数作为参数并通过调用它来使用它,没有什么特别的需要做。在higher_order内部,a_callback是传递进来的任何函数的本地名称(这里是my_callback);通过编写它们的名称、(、适当的参数和)来调用函数;因此,这就是higher_order需要做的一切,以便使用传递进来的函数。

编写高阶函数

假设我们试图定义def my_func(other_func, func_args):,其中other_func将是一个回调函数。在函数内部,other_func只是传递进来的回调函数的名称,并且调用它的方式与调用任何其他函数相同。我们需要一个名称(或任何其他求值为可调用对象的表达式),然后是(,然后是调用的任何适当参数,然后是)。例如,假设func_args应该是可调用对象的变量参数序列,我们可以通过解包调用中的参数进行此调用。因此:
def my_func(other_func, func_args):
    other_func(*func_args)

同样地,需要关键字参数的回调函数可以从另一个参数中接收它们,该参数将被传递一个dict(或其他映射),高阶函数可以使用**解包将其传递给回调函数。因此:
def my_func(other_func, func_args, func_kwargs):
    other_func(*func_args, **func_kwargs)

当然,我们决不局限于这种基本逻辑my_func像任何其他函数一样工作。在调用other_func之前或之后,它可以执行任何其他任意的工作;它可以return 或以其他方式使用other_func的结果;它可以多次调用other_func(或有条件地根本不调用它);它可以使用自己的逻辑来确定要传递给回调的参数(甚至完全在本地确定它们而不具有像func_argsfunc_kwargs的参数),等等。

将回调函数传递给高阶函数

为了使用这个高阶函数,调用代码需要两个东西:一个适当的可调用对象作为回调(即其签名必须与高阶函数如何调用它兼容),以及适当的调用高阶函数的代码。

继续上面的例子,假设我们有一个类似于以下的回调函数:

def my_callback(a, b, /, **c):
    print(f'a = {a}; b = {b}; c = {c}')

由于之前的my_func将在调用时使用***,应用于来自调用者的输入,因此对于my_callback的签名没有特定的限制。但是,由于my_func将从*func_args接收ab参数,并且因为my_func标记这些参数为位置参数, 所以传递给my_funcfunc_args需要是长度为2的序列。(func_kwargs本来就应该是字典;它将被解包以供回调调用,然后回调将再次打包它。

因此:

def my_func(other_func, func_args, func_kwargs):
    other_func(*func_args, **func_kwargs)

def my_callback(a, b, /, **c):
    print(f'a = {a}; b = {b}; c = {c}')

# call `my_func`, giving it the callback `my_callback`
# as well as appropriate arguments to call the callback:
my_func(my_callback, [1, 2], {'example': 3})

其他类型的回调函数

由于高阶函数只是简单地调用回调函数,它实际上并不关心回调函数是否为函数。利用鸭子类型的优势,我们也可以传递例如类等其他类型的参数。这对于使用回调函数进行“类型检查”的高阶函数特别有用(例如标准库argparse就是这样做的):

def ask_user_for_value(type_checker):
    while True:
        try:
            return type_checker(input('give me some input: '))
        except Exception as e:
            print(f'invalid input ({e}); try again')

# using an existing type:
ask_user_for_value(int)

# using a custom function for verification:
def capital_letter(text):
    if len(text) != 1:
        raise ValueError('not a single character')
    if not (text.isupper() and text.isalpha()):
        raise ValueError('not an uppercase letter')
    return text

ask_user_for_value(capital_letter)

# using an enum: (in 3.11 this could use StrEnum)
from enum import Enum
class Example(Enum):
    ONE = 'one'
    TWO = 'two'

ask_user_for_value(Example)

# using a bound method of an instance:
class Exact: # only allow the specified string
    def __init__(self, value):
        self._value = value

    def check(self, value):
        if value != self._value:
            raise ValueError(f'must be {self._value!r}')
        return value

ask_user_for_value(Exact('password').check)

其他使用回调函数的方法

除了定义高阶函数之外,回调函数还可以简单地存储在数据结构中,例如listdict或作为某个类实例的属性,然后稍后再使用。关键的洞见是,在Python中函数是对象,因此它们可以以这种方式存储,并且任何求值为函数对象的表达式都可以用于调用该函数。

示例:

def my_callback():
    print('my_callback was called')

# in a list
funcs = [my_callback]
for i in funcs:
    i()

# in a dict
funcs = {'my_callback': my_callback}
funcs['my_callback']()

# in a class instance
class Example:
    def __init__(self, callback):
        self._callback = callback

    def use_callback(self):
        self._callback()

instance = Example()
instance.use_callback()

特殊情况:提供高阶函数不提供的参数

有时,我们想要使用现有的回调函数,但它需要除了高阶函数提供的参数之外的其他参数。当使用来自第三方代码的高阶函数时,这尤其重要。许多库专门设计为接受任意参数以转发到回调函数(例如标准库threading),但其他库则不然(例如,使用可调用对象而非字符串进行测试代码的标准库timeit模块)。

在后一种情况下,在将回调传递给高阶函数之前,必须将参数“绑定”到回调函数。

请参见Python参数绑定器以了解如何执行此操作 - 这超出了本答案的范围。

当回调被存储以便以其他方式稍后使用时(例如,作为在创建Tkinter中的按钮时提供的command),相同的逻辑当然适用。

0
def x(a):
    print(a)
    return a

def y(a):
    return a

y(x(1))

2
请考虑在您的答案中添加一些解释。 - HMD
1
你在这里没有将函数作为参数传递,而是返回值。 - Bob Bobster

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