DevGang
Авторизоваться

Передача функции в качестве аргумента другой функции в 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 выражения, имейте в виду, что:

  1. Лямбда-выражение - это специальный синтаксис для создания функции и передачи ее другой функции в одной строке кода.
  2. Лямбда-функции аналогичны всем другим функциональным объектам: ни один не является более особенным, чем другие, и оба могут быть переданы

Все функции в Python могут быть переданы в качестве аргумента другой функции (это единственное назначение лямбда-функций).

Типичный пример: функции key

Помимо встроенной функции filter, где вы когда-нибудь увидите функцию, переданную в другую функцию? Вероятно, наиболее распространенное место, где вы увидите это в самом Python, - это функция key .

Это общее соглашение для функций, которые принимают итерируемые для сортировки / упорядочения, чтобы также принимать именованный аргумент с именем key. Аргумент key должен быть функцией или другим вызываемым объектом.

Функции sortedmin и 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 принимаемый sortedmin и 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

Источник:

#Python
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться