В Python есть встроенная функция sorted(), которая сортирует список. По умолчанию эта функция сортирует список с помощью сравнения элементов оператором <
. Подобная сортировка подходит для простых ситуаций, например для числовых значений, но перестает работать в случае более сложного сравнения, например тогда, когда каждый элемент списка — словарь.
Давайте вообразим ситуацию: на вход в программу приходит список пользователей, который нужно отсортировать по возрасту и вывести на экран.
users = [
{ 'name': 'Igor', 'age': 19 },
{ 'name': 'Danil', 'age': 1 },
{ 'name': 'Vovan', 'age': 4 },
{ 'name': 'Matvey', 'age': 16 },
]
Сортировка по умолчанию не может правильно отсортировать подобный список. Причем это касается любого вида сортировки, который мог бы нам понадобиться. Мы можем захотеть сортировать по любому параметру (или даже по набору параметров) и в любом порядке. Сортировки нужны часто, и многие из них довольно сложны.
В языках, где функции не являются данными (объектами первого рода), нам пришлось бы для каждого вида сортировки реализовывать свою собственную функцию sorted()
. Но в Python есть способ лучше. Посмотрим на сигнатуру функции sorted()
:
sorted(iterable, /, *, key=None, reverse=False)
Функция принимает необязательный параметр key
— функцию, которая указывает по какому признаку сортировать элементы. Общая идея состоит в том, что нам не нужно реализовывать алгоритм сортировки каждый раз для каждой ситуации, ведь он не меняется. Всё, что меняется — элементы, которые сравниваются между собой в процессе сортировки. И функция sorted()
делегирует взаимодействие с этими элементами вызываемому коду:
users = [
{ name: 'Igor', age: 19 },
{ name: 'Danil', age: 1 },
{ name: 'Vovan', age: 4 },
{ name: 'Matvey', age: 16 },
]
def sort_key(elem):
return elem['age']
sorted(users, key=sort_key)
print(users)
# => [ { 'name': 'Danil', 'age': 1 },
# { 'name': 'Vovan', 'age': 4 },
# { 'name': 'Matvey', 'age': 16 },
# { 'name': 'Igor', 'age': 19 } ]
Функция sorted()
выполняет всю работу по непосредственному перемещению элементов в списке. Но то, по какому признаку сравнивать — зависит от программиста. Достигается подобная схема за счёт той самой пользовательской функции, которая передаётся при вызове sorted()
. Эта функция принимает на вход один параметр — sorted()
отдаёт в неё элемент коллекции из которого извлекается признак сравнения. В нашем случае элементы — пользователи. Ваша задача — внутри этой функции извлечь параметр сравнения, а sorted()
производит их сортировку.
Из кода видно, что внутри функции сравнение идёт по свойству age
переданных пользователей.
Функция sorted()
относится к так называемым функциям высшего порядка (higher order functions). Функции высшего порядка — это функции, которые либо принимают, либо возвращают другие функции, либо делают всё сразу. Такие функции, как правило, реализуют некий обобщённый алгоритм (например, сортировку), а ключевую часть логики делегируют программисту через функцию. Главный плюс от применения таких функций — сокращение дублирования.
У функции, которая передается внутрь sorted()
есть свое название. Подобные функции называют колбеками (callback, обратный вызов). Колбеком становится любая функция, которая вызывается не напрямую программистом. Ее вызывает какая-то функция, в которую мы передаем наш колбек.
В примере выше необязательно создавать именованную функцию. Зачастую в таких ситуациях описывают лямбду-функцию сразу в параметре. Типичное использование выглядит как прямая передача функции в функцию:
sorted(users, key=lambda user: user['age'])
# [ { 'name': 'Danil', 'age': 1 },
# { 'name': 'Vovan', 'age': 4 },
# { 'name': 'Matvey', 'age': 16 },
# { 'name': 'Igor', 'age': 19 } ]
Осталось рассмотреть то, как происходит вызов внутри. С точки зрения синтаксиса ничего нового не будет.
def say(fn):
message = fn()
print(message)
# или так
# say = lambda fn: print(fn())
my_callback_fn = lambda: 'hi!'
say(my_callback_fn) # => hi!
Функция say()
делает вызов функции, находящейся внутри параметра fn
. В нашем примере функция возвращает строку, которая тут же выводится на экран.
Функции высшего порядка настолько удобны в большинстве языков, что практически целиком могут заменить использование циклов. Например, код на Python может выглядеть так:
# Просто демонстрация
# Разбирать его не надо
'\n'.join(
map(
lambda user: f"{user['name']} is {user['age']} years old",
filter(lambda user: user['age'] >= 16, users)
)
)
# => Igor is 19 years old
# Matvey is 16 years old
В следующих уроках мы рассмотрим три самые главные функции высшего порядка, которыми можно решать практически любые задачи. Две из них используются в примере выше, это map()
и filter()
, а третья — reduce()
. Они все доступны в стандартной библиотеке Python.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.