假设我有一些类似的代码:def myfunc(anotherfunc, extraArgs): # 以某种方式在这里调用“anotherfunc”,并将“extraArgs”传递给它我想将另一个现有函数作为另一个函数传递...
假设我有一些如下代码:
def myfunc(anotherfunc, extraArgs):
# somehow call `anotherfunc` here, passing it the `extraArgs`
pass
我想将另一个现有函数作为 anotherfunc
参数传递,并将参数列表或元组作为传递 extraArgs
,并使用 myfunc
这些参数调用传入的函数。
这可能吗?我该怎么做?
是的,这是可能的。
在问题中的例子中, anotherfunc
的参数 myfunc
回调 的一个例子 , 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
- just the function name, not with parentheses after it 。错误的写法 higher_order(my_callback())
意味着 先 call my_callback
first ,然后将 返回值 ( here, that would be None
)传递给 higher_order
。这将导致 TypeError
,因为 None
is not callable .
在函数本身中, 无需执行任何特殊操作 即可接受另一个函数作为参数,也无需通过调用它来使用它。 内部 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)
类似地,需要关键字参数的回调可以从将传递 a dict
(或其他映射)的另一个参数接收它们,HOF 可以通过 **
解包将其传递给回调。因此:
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_args
的参数 func_kwargs
),等等。
为了使用这个 HOF,调用代码需要两样东西:一个适当的可调用函数来作为回调传递(即,它的签名必须与 HOF 调用它的方式兼容),以及调用 HOF 本身的适当代码。
继续上面的例子,假设我们有一个回调函数
def my_callback(a, b, /, **c):
print(f'a = {a}; b = {b}; c = {c}')
由于前者 my_func
将使用 *
和 **
进行调用,应用于来自调用者的输入,因此对的签名没有特别的限制 my_callback
。但是,由于 my_func
中 a
接收 b
和 *func_args
,并且由于将 my_func
这些参数标记为仅位置的 , func_args
传递给的 my_func
将需要是长度为 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})
由于 HOF 只是 调用 回调,因此它实际上并不关心回调是否是函数。利用 duck typing ,我们还可以传递例如类。这对于使用回调进行“类型检查”的 HOF 特别有用(例如标准库 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)
除了定义 HOF 之外,回调函数还可以简单地存储在数据结构中(例如 list
, dict
或作为某个类实例的属性),然后稍后使用。关键的见解是 函数是 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()
有时,我们想使用现有的回调函数,但它需要 HOF 提供的参数以外的其他参数。当使用来自第三方代码的 HOF 时,这一点尤其重要。许多库专门设计为接受任意参数以转发给回调(例如, the standard library threading
),但其他库则不是(例如,使用 the standard library timeit
module 和可调用函数而不是字符串来测试代码)。
在后一种情况下,参数在传递给 HOF 之前必须先与回调“绑定”。
See Python Argument Binders to understand how to do this - 这超出了本答案的范围。
当然,当存储回调以供以后以其他方式使用时(例如, command
在 Tkinter 中创建按钮 a Button
in Tkinter ),也适用相同的逻辑。