Передача функции в качестве аргумента другой функции в Python
Один из самых волнующих фактов, которые мы узнаем в этой статье, заключается в том, что вы можете передавать функции в другие функции. Вы можете передавать функции, потому что в Python функции являются объектами.
Скорее всего, вам не нужно знать об этом на первой неделе использования Python, но, углубившись в Python, вы обнаружите, что очень удобно понимать, как передавать функцию в другую функцию.
На функции можно ссылаться
Если вы попытаетесь использовать функцию без круглых скобок после нее, Python не будет жаловаться, но он также не сделает ничего полезного:
>>> def greet():
... print("Hello world!")
...
>>> greet
Это относится и к методам (методы - это функции, которые живут на объектах):
>>> numbers = [1, 2, 3]
>>> numbers.pop
Python позволяет нам ссылаться на эти функциональные объекты так же, как мы можем ссылаться на строку, число или объект range
:
>>> "hello"
'hello'
>>> 2.5
2.5
>>> range(10)
range(0, 10)
Поскольку мы можем ссылаться на функции как любой другой объект, мы можем указать переменной ссылку на функцию:
>>> numbers = [2, 1, 3, 4, 7, 11, 18, 29]
>>> gimme = numbers.pop
Переменная gimme
теперь указывает на метод pop
в нашем списке numbers
. Поэтому, если мы вызовем gimme
, то сделаем то же самое, что и вызов numbers.pop
:
>>> gimme()
29
>>> numbers
[2, 1, 3, 4, 7, 11, 18]
>>> gimme(0)
2
>>> numbers
[1, 3, 4, 7, 11, 18]
>>> gimme()
18
Обратите внимание, что мы не сделали новую функцию. Мы только что указали переменной gimme
ссылку на функцию numbers.pop
:
>>> gimme
>>> numbers.pop
Вы даже можете хранить функции внутри структур данных и затем ссылаться на них позже:
>>> def square(n): return n**2
...
>>> def cube(n): return n**3
...
>>> operations = [square, cube]
>>> numbers = [2, 1, 3, 4, 7, 11, 18, 29]
>>> for i, n in enumerate(numbers):
... action = operations[i % 2]
... print(f"{action.__name__}({n}):", action(n))
...
square(2): 4
cube(1): 1
square(3): 9
cube(4): 64
square(7): 49
cube(11): 1331
square(18): 324
cube(29): 24389
Нередко возникает необходимость взять функцию и дать ей другое имя или сохранить ее внутри структуры данных, но Python позволяет нам делать это, потому что функции можно передавать, как и любой другой объект.
Функции могут быть переданы в другие функции
Функции, как и любой другой объект, могут быть переданы в качестве аргумента другой функции.
Например, мы могли бы определить функцию:
>>> def greet(name="world"):
... """Greet a person (or the whole world by default)."""
... print(f"Hello {name}!")
...
>>> greet("Trey")
Hello Trey!
А затем передайте ее во встроенную функцию help
, чтобы посмотреть, что она делает:
>>> help(greet)
Help on function greet in module __main__:
greet(name='world')
Greet a person (or the whole world by default).
И мы можем передать функцию самой себе (да, это странно), которая преобразует ее в строку:
>>> greet(greet)
Hello !
На самом деле в Python встроено довольно много функций, которые специально предназначены для принятия других функций в качестве аргументов.
Встроенная функция filter
принимает в качестве аргумента две вещи: function
и iterable
.
>>> help(filter)
| filter(function or None, iterable) --> filter object
|
| Return an iterator yielding those items of iterable for which function(item)
| is true. If function is None, return the items that are true.
Данная итерация (список, кортеж, строка и т.д.) Зацикливается, и данная функция вызывается для каждого элемента в этой итерации: всякий раз, когда функция возвращает True
(или другое истинное значение), элемент включается в вывод filter
.
Так что, если мы передадим filter
функцию is_odd
(которая возвращает True
, если заданное число нечетное) и список чисел, нам вернутся все числа, которые не кратны 2-ум.
>>> numbers = [2, 1, 3, 4, 7, 11, 18, 29]
>>> def is_odd(n): return n % 2 == 1
...
>>> filter(is_odd, numbers)
>>> list(filter(is_odd, numbers))
[1, 3, 7, 11, 29]
Возвращаемый filter
объект - это ленивый итератор, поэтому нам нужно было преобразовать его в list
, чтобы реально увидеть его вывод.
Поскольку функции могут быть переданы в функции, это также означает, что функции могут принимать другую функцию в качестве аргумента. Функция filter
принимает первым аргументом функцию. Вы можете представить filter
так же, как функцию приведенную ниже:
def filter(predicate, iterable):
return (
item
for item in iterable
if predicate(item)
)
Эта функция ожидает, что аргумент predicate
будет функцией (технически это может быть любой вызываемый объект). Когда мы вызываем эту функцию (с predicate(item)
), мы передаем ей один аргумент, а затем проверяем возвращаемое значение.
Лямбда-функции являются примером этого
Лямбда-выражение - это специальный синтаксис в Python для создания анонимной функции. Когда вы создаете лямбда-выражение, возвращаемый объект называется лямбда-функцией.
>>> is_odd = lambda n: n % 2 == 1
>>> is_odd(3)
True
>>> is_odd(4)
False
Лямбда-функции во многом похожи на обычные функции Python, с некоторыми оговорками.
В отличие от других функций, лямбда-функции не имеют имени (их имя отображается как lambda
). У них также не может быть строк документации, и они могут содержать только одно выражение Python.
>>> add = lambda x, y: x + y
>>> add(2, 3)
5
>>> add
at 0x7ff244852f70>
>>> add.__doc__
Вы можете думать о лямбда-выражении как о ярлыке для создания функции, которая будет оценивать одно выражение Python и возвращать результат этого выражения.
Таким образом, определение лямбда-выражения на самом деле не оценивает это выражение: оно возвращает функцию, которая может вычислить это выражение позже.
>>> greet = lambda name="world": print(f"Hello {name}")
>>> greet("Trey")
Hello Trey
>>> greet()
Hello world
Я хотел бы отметить, что все три из приведенных выше примеров являются плохими примерами lambda
. Если вы хотите, чтобы имя переменной указывало на объект функции, который вы можете использовать позже, вы должны использовать def
для ее определения: это обычный способ определения функции.
>>> def is_odd(n): return n % 2 == 1
...
>>> def add(x, y): return x + y
...
>>> def greet(name="world"): print(f"Hello {name}")
...
Лямбда-выражения предназначены для случаев, когда мы хотим определить функцию и сразу передать ее в другую функцию.
Например, здесь мы используем filter
для получения четных чисел, но мы используем лямбда-выражения, поэтому нам не нужно определять функцию is_even
перед ее использованием:
>>> numbers
[2, 1, 3, 4, 7, 11, 18, 29]
>>> list(filter(lambda n: n % 2 == 0, numbers))
[2, 4, 18]
Это наиболее подходящее использование лямбда-выражений: передача функции в другую функцию при определении этой переданной функции в одной строке кода.
Нравится ли вам этот синтаксис или нет, вы должны знать, что этот синтаксис является просто ярлыком для создания функции.
Всякий раз, когда вы видите lambda
выражения, имейте в виду, что:
- Лямбда-выражение - это специальный синтаксис для создания функции и передачи ее другой функции в одной строке кода.
- Лямбда-функции аналогичны всем другим функциональным объектам: ни один не является более особенным, чем другие, и оба могут быть переданы
Все функции в Python могут быть переданы в качестве аргумента другой функции (это единственное назначение лямбда-функций).
Типичный пример: функции key
Помимо встроенной функции filter
, где вы когда-нибудь увидите функцию, переданную в другую функцию? Вероятно, наиболее распространенное место, где вы увидите это в самом Python, - это функция key .
Это общее соглашение для функций, которые принимают итерируемые для сортировки / упорядочения, чтобы также принимать именованный аргумент с именем key
. Аргумент key
должен быть функцией или другим вызываемым объектом.
Функции sorted
, min
и max
следуют этому соглашению принятия функции key
:
>>> fruits = ['kumquat', 'Cherimoya', 'Loquat', 'longan', 'jujube']
>>> def normalize_case(s): return s.casefold()
...
>>> sorted(fruits, key=normalize_case)
['Cherimoya', 'jujube', 'kumquat', 'longan', 'Loquat']
>>> min(fruits, key=normalize_case)
'Cherimoya'
>>> max(fruits, key=normalize_case)
'Loquat'
Эта key функция вызывается для каждого значения в итерируемом объекте, а возвращаемое значение используется для упорядочения / сортировки каждого из итерируемых элементов. Вы можете думать об этой функции как о вычислении ключа сравнения для каждого элемента итерируемого объекта.
В приведенном выше примере наш ключ сравнения возвращает строку в нижнем регистре, поэтому каждая строка сравнивается по версии в нижнем регистре (что приводит к регистронезависимому порядку).
Для этого мы использовали функцию normalize_case
, но то же самое можно сделать, используя str.casefold
:
>>> fruits = ['kumquat', 'Cherimoya', 'Loquat', 'longan', 'jujube']
>>> sorted(fruits, key=str.casefold)
['Cherimoya', 'jujube', 'kumquat', 'longan', 'Loquat']
Примечание: этот трюк немного странный, если вы не знакомы с тем, как работают классы. Классы хранят несвязанные методы, которые при вызове будут принимать экземпляр этого класса. Обычно мы печатаем my_string.casefold()
, но Python переводит это в str.casefold(my_string)
.
Здесь мы находим строку с наибольшим количеством букв в ней:
>>> max(fruits, key=len)
'Cherimoya'
Если есть несколько максимумов или минимумов, выигрывает самый ранний (вот как min / max
работа):
>>> fruits = ['kumquat', 'Cherimoya', 'Loquat', 'longan', 'jujube']
>>> min(fruits, key=len)
'Loquat'
>>> sorted(fruits, key=len)
['Loquat', 'longan', 'jujube', 'kumquat', 'Cherimoya']
Вот функция, которая будет возвращать кортеж из 2 элементов, содержащий длину заданной строки и нормализованную по регистру версию этой строки:
def length_and_alphabetical(string):
"""Return sort key: length first, then case-normalized string."""
return (len(string), string.casefold())
Мы могли бы передать функцию length_and_alphabetical
в качестве аргумента key
, чтобы сначала отсортировать наши строки по их длине sorted
, а затем по их нормализованному регистру:
>>> fruits = ['kumquat', 'Cherimoya', 'Loquat', 'longan', 'jujube']
>>> fruits_by_length = sorted(fruits, key=length_and_alphabetical)
>>> fruits_by_length
['jujube', 'longan', 'Loquat', 'kumquat', 'Cherimoya']
Это основывается на том факте, что операторы Python для упорядочивания выполняют глубокие сравнения.
Другие примеры передачи функции в качестве аргумента
Аргумент key
принимаемый sorted
, min
и max
это только один общий пример передачи функций в функцию.
Еще две встроенные функции Python - map
и filter
.
Мы уже видели, что filter
будет фильтровать наш список на основе возвращаемого значения данной функции.
>>> numbers
[2, 1, 3, 4, 7, 11, 18, 29]
>>> def is_odd(n): return n % 2 == 1
...
>>> list(filter(is_odd, numbers))
[1, 3, 7, 11, 29]
Функция map
будет вызывать данную функцию по каждому пункту в данный итерации и использовать результат этой функции вызова в качестве нового пункта:
>>> list(map(is_odd, numbers))
[False, True, True, False, True, True, False, True]
Например, здесь мы конвертируем числа в строки и возводим в квадрат:
>>> list(map(str, numbers))
['2', '1', '3', '4', '7', '11', '18', '29']
>>> list(map(lambda n: n**2, numbers))
[4, 1, 9, 16, 49, 121, 324, 841]
Аналогично map
, и filter
, также есть TakeWhile и dropwhile от модуля itertools
. Первый аналогичен filter
, за исключением того, что останавливается, когда находит значение, для которого предикатная функция имеет значение false
. Второй делает противоположное: он включает значения только после того, как функция предиката стала ложной.
>>> from itertools import takewhile, dropwhile
>>> colors = ['red', 'green', 'orange', 'purple', 'pink', 'blue']
>>> def short_length(word): return len(word) < 6
...
>>> list(takewhile(short_length, colors))
['red', 'green']
>>> list(dropwhile(short_length, colors))
['orange', 'purple', 'pink', 'blue']
И есть functools.reduce и itertools.accumulate, которые оба вызывают 2-аргумент (функцию) для значений взвешенного суммирования:
>>> from functools import reduce
>>> from itertools import accumulate
>>> numbers = [2, 1, 3, 4, 7]
>>> def product(x, y): return x * y
...
>>> reduce(product, numbers)
168
>>> list(accumulate(numbers, product))
[2, 2, 6, 24, 168]
Класс defaultdict в модуле collections
является еще одним примером. Класс defaultdict
создает словарь, как объекты, которые никогда не бросит KeyError
, когда отсутствует ключ, но вместо этого будет автоматически добавлено новое значение в словаре.
>>> from collections import defaultdict
>>> counts = defaultdict(int)
>>> counts['jujubes']
0
>>> counts
defaultdict(, {'jujubes': 0})
Этот класс defaultdict
принимает вызываемую функцию (функцию или класс), которая будет вызываться для создания значения по умолчанию при каждом обращении к отсутствующему ключу.
Приведенный выше код работал, потому что int
возвращает 0
при вызове без аргументов:
>>> int()
0
Здесь используется значением по умолчанию list
, которое возвращает новый список при вызове без аргументов.
>>> things_by_color = defaultdict(list)
>>> things_by_color['purple'].append('socks')
>>> things_by_color['purple'].append('shoes')
>>> things_by_color
defaultdict(, {'purple': ['socks', 'shoes']})
Функция partial в модуле functools
является еще одним примером. partial
принимает функцию и любое количество аргументов и возвращает новую функцию (технически она возвращает вызываемый объект).
Вот пример partial
, используемый для «привязки» аргумента ключевого слова sep
к функции print
:
>>> print_each = partial(print, sep='\n')
Возвращенная функция print_each
теперь делает то же самое, как если бы она была вызвана print
с sep='\n'
:
>>> print(1, 2, 3)
1 2 3
>>> print(1, 2, 3, sep='\n')
1
2
3
>>> print_each(1, 2, 3)
1
2
3