Python callback with parameters

How does using a function (callback) as an argument to another function work in Python?

I want to pass another existing function as the anotherfunc argument, and a list or tuple of arguments as extraArgs , and have myfunc call the passed-in function with those arguments. Is this possible? How can I do it?

11 Answers 11

Yes, this is possible. myfunc can call the passed-in function like:

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

Here is a fully worked example:

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

@4dc0 I don’t think it’s directly documented; it’s just a natural consequence of how Python’s data model works. Everything that can be bound to a name is an object; objects can be passed to functions; functions can be bound to a name (that’s what happens when you use def ); therefore, functions can be passed to functions. That said, you may be interested in docs.python.org/3.11/howto/functional.html, which shows elementary techniques that make use of this reality, and shows off some of the standard library goodies designed to capitalize on it.

Here’s another way using *args (and optionally **kwargs as well):

def a(x, y): print(x, y) def b(other, function, *args, **kwargs): function(*args, **kwargs) print(other) b('world', a, 'hello', 'dude') 

Note that function , *args , and **kwargs must appear in that order and must be the last arguments to the function ( b ) that calls function .

Functions in Python are first-class objects. However, the function definition should be slightly different:

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

Sure, that is why python implements the following methods where the first parameter is a function:

  • map(function, iterable, . ) — Apply function to every item of iterable and return a list of the results.
  • filter(function, iterable) — Construct a list from those elements of iterable for which function returns true.
  • reduce(function, iterable [,initializer]) — Apply function of two arguments cumulatively to the items of iterable , from left to right, so as to reduce the iterable to a single value.
  • lambdas

See also: docs.python.org/3.11/howto/functional.html. The builtins also include sort , min and max which accept a key keyword argument that is expected to be a callable.

I don’t really think lambdas belong in the same category, though. They aren’t a built-in function (nor «method»), but a syntax; and a lambda’s first parameter definitely does not need to accept a function. In fact, a lambda doesn’t need to have parameters at all.

A function can be a parameter to another function, and a function can return another function.

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 

Decorators are very powerful in Python since they programmers to pass a function as an argument, and can also define one function inside another function.

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 

It’s possible to call two or more functions at once, by using a call to one function as an argument to the other:

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

This will cause myfunc to print the return value from the anotherfunc call (i.e., None ).

Читайте также:  Input integer value in java

Yes, this is possible. Use the function like any other: anotherfunc(*extraArgs) .

Summary

In the example in the question, the anotherfunc parameter for myfunc is an example of a callback, and myfunc is therefore an example of a higher-order function (hereafter, HOF).

A simple example of both sides of the equation — writing the HOF and giving it a callback — might look like:

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) 

Note carefully that the example passes my_callback — using just the function name, not with parentheses after it. Incorrectly writing higher_order(my_callback()) would mean to call my_callback first, and pass the return value (here, that would be None ) to higher_order . This will cause a TypeError , since None is not callable.

In the function itself, nothing special needs to be done in order to accept another function as a parameter, nor to use it by calling it. Inside higher_order , a_callback is the local name for whatever function was passed in (here, my_callback ); functions are called by writing their name, ( , the appropriate arguments, and ) ; so that is all that higher_order needs to do in order to use that passed-in function.

Writing a HOF

Suppose we attempt to define def my_func(other_func, func_args): , where other_func will be a callback. Within the function, other_func is simply a name for the callback that was passed in, and calling it works the same way as calling any other function. We need a name (or any other expression that evaluates to the callable that should be called), then ( , then any appropriate arguments for the call, then ) . Supposing for example that func_args should be a sequence of variable arguments to the callable, we can make this call by unpacking the arguments in the call. Thus:

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

Similarly, a callback that requires keyword arguments could receive them from another parameter that will be passed a dict (or other mapping), which the HOF could pass to the callback with ** unpacking. Thus:

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

Of course, we are by no means limited to such basic logic. my_func works like any other function. It could do any other arbitrary work before or after calling other_func ; it could return or otherwise make use of the other_func result; it could call other_func more than once (or conditionally not at all); it could use its own logic to determine arguments to pass to the callback (perhaps even determine them locally and not have parameters like func_args or func_kwargs at all), etc.

Читайте также:  M java apps com

Passing a callback function to a HOF

In order to use this HOF, the calling code requires two things: an appropriate callable to pass as the callback (i.e., its signature must be compatible with how the HOF will call it), and the appropriate code for a call to the HOF itself.

Continuing the above example, suppose we have a callback like

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

Since the previous my_func will use * and ** for the call, applied to input from the caller, there is no particular restriction on the signature of my_callback . However, since my_func will receive the a and b arguments from *func_args , and since my_func marks these parameters as positional-only, the func_args passed to my_func will need to be a sequence of length 2. ( func_kwargs is supposed to be a dictionary anyway; it will be unpacked for the call to the callback, and then the callback will pack it again.

def my_func(other_func, func_args, func_kwargs): other_func(*func_args, **func_kwargs) def my_callback(a, b, /, **c): print(f'a = ; b = ; 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], ) 

Other kinds of callbacks

Since the HOF simply calls the callback, it doesn’t actually care whether the callback is a function. Taking advantage of duck typing, we can also pass e.g. a class. This is especially useful for HOFs that use a callback for «type-checking» (e.g. the standard library argparse does this):

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 (); 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 ') return value ask_user_for_value(Exact('password').check) 

Other ways to use callbacks

Aside from defining a HOF, a callback function can simply be stored in a data structure — such as a list , dict or as an attribute of some class instance — and then used later. The key insight is that functions are objects in Python, so they can be stored in this way, and any expression that evaluates to the function object can be used to call the function.

def my_callback(): print('my_callback was called') # in a list funcs = [my_callback] for i in funcs: i() # in a dict funcs = 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() 

Special case: providing arguments that the HOF won’t

Sometimes, we want to use an existing callback function, but it requires additional arguments besides what the HOF will provide. This is especially important when working with a HOF that comes from third-party code. Many libraries are specifically designed to accept arbitrary parameters to forward to the callback (for example, the standard library threading ), but others are not (for example, using the standard library timeit module with a callable rather than a string for the code to test).

Читайте также:  Curl php ssl tls

In the latter case, arguments must be «bound» to the callback before passing it to the HOF.

See Python Argument Binders to understand how to do this — it is beyond the scope of this answer.

The same logic, of course, applies when a callback is stored for later use in other ways (e.g. as a command provided when creating a Button in Tkinter).

Источник

Коллбэки в Python

Функция — это объект. Такой же, как числа или строки. И так же, как числа или строки, их можно складывать в переменные.

Рассмотрим пример — возьмём встроенную функцию len() , которая считает длину переданного ей аргумента:

a = len('Привет!') b = len print(a) # 7 print(b) # 

В первом случае мы вызывали (читай, запустили) функцию len , передали ей строку и получили результат её работы: 7 . Его мы и записали в переменную a .

Во втором случае функцию не вызывали, а сразу же записали в переменную. Теперь в b лежит len . Мы даже можем вызывать её оттуда:

Получается, что если не вызывать функцию (не использовать круглые скобки), то ей можно оперировать как любым другим типом данных, как числом или строкой. А если функцию можно записывать в переменные, значит её можно передать аргументом на вход другой функции. Вот мы и добрались до коллбэков.

Что такое коллбэк

Коллбэк — это функция, которая передаётся на вход другой функции (или другому участку кода), чтобы её запустили в ответ на какое-то событие. С помощью этого приёма работают чатботы и интерактивные веб-странички: пользователь нажимает на кнопку, его действие генерирует событие и на событие реагирует коллбек (функция-обработчик). Рассмотрим пример:

from time import time_ns def timeit(function): start_time = time_ns() function() end_time = time_ns() return end_time - start_time timeit(print) # 28278 наносекунд 

Функция timeit принимает на вход любую другую функцию и засекает время её выполнения. Функция time_ns() засекает текущее время.

Как вызвать коллбэк

Коллбэк вызывается так же, как и обычная функция:

def callback_function(): print("Я коллбэк") callback = callback_fuction callback() 

Мы положили функцию в переменную, а потом вызвали её, поставив скобки. На экран вывелось “Я коллбэк”.

Создадим список из коллбэков:

def callback_1(): print("Я первый коллбэк") def callback_2(): print("Я второй коллбэк") def callback_3(): print("Я третий коллбэк") callbacks = [callback_1, callback_2, callback_3] # Обратите внимание, что скобок нет. # Мне нужны не результаты вызова функций, а сами функции for callback in callbacks: callback() # а вот здесь я уже вызываю функции 

На экран выведутся все 3 надписи по порядку:

Я первый коллбэк Я второй коллбэк Я третий коллбэк 

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Источник

Оцените статью