Список чисел легко отсортировать: sorted(numbers) расставит их по возрастанию, и вопросов не возникнет. Но в реальных данных редко бывают голые числа. Обычно это список пар "имя, оценка" или список словарей с несколькими полями — и Python должен понять, по какому именно полю сравнивать. Заодно часто нужно не просто получить отсортированный результат, а ещё и пронумеровать его или сопоставить с другим списком. Для этого в Python есть параметр key у sorted() и две вспомогательные функции — enumerate() и zip(). С lambda вы уже знакомы, так что key дастся легко.
sorted() и list.sort(): новый список или перестановка на месте
Представьте, что у вас на столе лежит колода карточек с именами студентов. Есть два способа их упорядочить: переписать имена по алфавиту на новый лист, оставив исходную колоду как была, или прямо перетасовать саму колоду. Оба способа дают отсортированный результат, но по-разному обращаются с оригиналом.
В Python за первый способ отвечает встроенная функция sorted(): она принимает любую последовательность и возвращает новый отсортированный список, а исходные данные не трогает. За второй способ отвечает метод list.sort(): он переставляет элементы прямо внутри списка и ничего не возвращает.
scores = [70, 95, 60, 85]
result = sorted(scores)
print(result)
print(scores)
scores.sort()
print(scores)
Разберём построчно:
scores = [70, 95, 60, 85]— список с исходным порядком оценок.result = sorted(scores)— вызываем функциюsorted()и передаём ей список. Она строит новый отсортированный список и возвращает его; мы сохраняем результат в переменнуюresult.print(result)— выведет[60, 70, 85, 95]: новый список отсортирован по возрастанию.print(scores)— выведет[70, 95, 60, 85]: исходный список не изменился,sorted()работает с копией порядка, а не с самим списком.scores.sort()— вызываем методsort()прямо у спискаscores. Он ничего не возвращает (если написатьx = scores.sort(), вxокажетсяNone), но переставляет элементы внутри самого списка.print(scores)— теперь выведет[60, 70, 85, 95]: список изменился на месте.
Как выбрать: если исходный порядок ещё понадобится или вы сортируете не список (строку, кортеж, результат другой функции) — берите sorted(). Если оригинал больше не нужен и хочется сэкономить память на копии — .sort().
Параметр key: по какому признаку сравнивать
Пока элементы простые числа, сравнивать их легко: 60 меньше 70, и так далее. Но если каждый элемент — это пара "имя и оценка" или словарь с несколькими полями, Python не знает сам, что именно сравнивать: имена по алфавиту или оценки по числу. Нужно явно объяснить, какую часть каждого элемента брать для сравнения.
Для этого у sorted() (и у .sort()) есть именованный параметр key. Ему передают функцию одного аргумента: для каждого элемента списка Python вызовет эту функцию и будет сравнивать не сами элементы, а то, что функция вернула. Часто эту функцию удобно задать через lambda, которую вы уже разбирали.
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]
by_score = sorted(students, key=lambda s: s[1])
print(by_score)
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]— список кортежей, у каждого два элемента: имя (индекс0) и оценка (индекс1).key=lambda s: s[1]— передаём функцию, которая принимает один элемент спискаs(то есть один кортеж вроде("Alice", 85)) и возвращаетs[1]— оценку. Сама функция ничего не сортирует, она только "подсказывает"sorted(), что сравнивать.sorted(students, key=...)— для каждого кортежа Python вызывает эту lambda, получает число (оценку) и сравнивает уже эти числа, а не кортежи целиком.print(by_score)— выведет[('Carol', 78), ('Alice', 85), ('Bob', 92)]: кортежи расставлены по возрастанию оценки, хотя сравнивались не они сами, а вторые элементы каждого из них.
Тот же приём работает и со списком словарей — только вместо индекса кортежа берём значение по ключу словаря:
students = [
{"name": "Alice", "grade": 85},
{"name": "Bob", "grade": 92},
]
by_grade = sorted(students, key=lambda s: s["grade"])
print(by_grade[0]["name"])
key=lambda s: s["grade"]— теперьsна каждом шаге — это один словарь вроде{"name": "Alice", "grade": 85}, аs["grade"]достаёт из него число по ключу"grade".by_grade[0]["name"]— после сортировки берём первый элемент нового списка (студента с самой низкой оценкой) и его поле"name". ВыведетAlice.
reverse=True: порядок от большего к меньшему
По умолчанию сортировка всегда идёт от меньшего к большему, будь то числа или буквы алфавита. Часто нужен обратный порядок — например, рейтинг, где на первом месте должен быть лучший результат, а не худший.
Для этого у sorted() есть ещё один именованный параметр — reverse. Он принимает True или False и ничего не меняет в самом сравнении: Python всё так же сравнивает элементы (или результат key), а потом просто разворачивает готовый порядок.
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]
top = sorted(students, key=lambda s: s[1], reverse=True)
print(top)
key=lambda s: s[1]— как и раньше, сравниваем по оценке (второй элемент кортежа).reverse=True— после сравнения по ключу Python выстраивает результат в обратном порядке: от большей оценки к меньшей.print(top)— выведет[('Bob', 92), ('Alice', 85), ('Carol', 78)]: список начинается с лучшего результата.
Совет: key и reverse — это именованные аргументы, их порядок при вызове не важен, важно только писать их название: sorted(data, reverse=True, key=...) сработает так же, как и в обратном порядке.
enumerate(): получить номер вместе со значением
Когда вы перебираете список в цикле for, вы получаете сами значения, но не их порядковый номер. Если для рейтинга или таблицы нужно вывести ещё и номер места — "1. Bob", "2. Alice" — приходится заводить отдельный счётчик и вручную увеличивать его на каждом шаге. Это лишний код и лишний шанс ошибиться.
Встроенная функция enumerate() решает это за вас: она оборачивает любую последовательность так, что на каждом шаге цикла вы получаете пару — номер и значение. По умолчанию нумерация начинается с 0, но можно задать другое начало через параметр start.
names = ["Bob", "Alice", "Carol"]
for i, name in enumerate(names, start=1):
print(f"{i}. {name}")
enumerate(names, start=1)— оборачивает списокnamesи на каждом шаге отдаёт пару (номер, элемент). Параметрstart=1говорит начать нумерацию с1, а не с0.for i, name in ...— цикл сразу распаковывает каждую пару в две переменные:iполучает номер,name— сам элемент списка.print(f"{i}. {name}")— собирает строку из номера и имени.
Программа выведет:
1. Bob
2. Alice
3. Carol
zip(): два списка бок о бок
Иногда данные приходят не одним списком пар, а двумя отдельными списками: список имён и список оценок, где первое имя относится к первой оценке, второе — ко второй и так далее. Перебирать их синхронно по индексу через range(len(...)) можно, но громоздко и легко ошибиться в индексах.
Функция zip() берёт несколько последовательностей и на каждом шаге отдаёт кортеж из их элементов с одинаковой позицией: первый элемент первой последовательности с первым элементом второй, второй со вторым, и так далее. Название — от англ. "молния" (застёжка), как раз по принципу "зубчик к зубчику".
names = ["Alice", "Bob", "Carol"]
grades = [85, 92, 78]
pairs = list(zip(names, grades))
print(pairs)
zip(names, grades)— соединяет два списка по позиции:"Alice"с85,"Bob"с92,"Carol"с78. Сам по себеzip()возвращает не список, а особый объект, который отдаёт пары по одной за раз.list(...)— оборачиваем результат вlist(), чтобы сразу получить обычный список кортежей и можно было его напечатать или сохранить.print(pairs)— выведет[('Alice', 85), ('Bob', 92), ('Carol', 78)].
Список, полученный через zip(), — это уже знакомые кортежи, поэтому его можно сразу сортировать через key и нумеровать через enumerate(), объединяя все три инструмента в одном решении.
Сортировка сразу по двум признакам
Иногда одного признака мало: нужно отсортировать по оценке, а тех, у кого оценка совпала, — ещё и по имени. Заводить для этого несколько отдельных сортировок не нужно.
key может вернуть не одно значение, а кортеж из нескольких. Python сравнивает такие кортежи по очереди: сначала первый элемент, и только если он совпал — смотрит на второй. Это тот же порядок сравнения, что и у слов в словаре: сначала первая буква, при совпадении — вторая.
students = [("Bob", 90), ("Alice", 90), ("Carol", 78)]
ranking = sorted(students, key=lambda s: (-s[1], s[0]))
print(ranking)
key=lambda s: (-s[1], s[0])— для каждого кортежа строим новый кортеж-ключ из двух частей:-s[1]— оценка со знаком минус,s[0]— имя.- Минус перед
s[1]— способ получить убывание безreverse=True: чем выше оценка, тем меньше её отрицательное значение, аsorted()всегда сравнивает по возрастанию ключа. - Второй элемент ключа,
s[0](имя), сработает только тогда, когда у двух студентов совпала оценка — тогда сравнение "провалится" на второй уровень и разберёт их по алфавиту. print(ranking)— выведет[('Alice', 90), ('Bob', 90), ('Carol', 78)]: у Alice и Bob одинаковая оценка 90, поэтому между собой они расставлены по алфавиту, а Carol с меньшей оценкой — в конце.
Частые ошибки
Сохранить результат .sort() в переменную
result = scores.sort() — в result окажется None, потому что .sort() ничего не возвращает, а меняет список scores напрямую. Нужный список — это уже изменённый scores, а не то, что вернул .sort().
Вызвать функцию вместо передачи её в key
key=s[1] вместо key=lambda s: s[1] — выдаст ошибку, потому что key ждёт функцию, которую сам вызовет для каждого элемента, а не готовое значение. Скобки вокруг lambda лучше не забывать.
Перепутать местами номер и значение в enumerate
for name, i in enumerate(names): — enumerate() всегда отдаёт пару (номер, значение) именно в этом порядке, поэтому первой переменной должен идти номер: for i, name in enumerate(names):.
Ждать от zip() пары, если списки разной длины
zip([1, 2, 3], [10, 20]) — вернёт только две пары. zip() останавливается там, где заканчивается самая короткая последовательность, лишние элементы длинной просто отбрасываются без ошибки.
Что важно запомнить
- sorted(data) возвращает новый список, оригинал не трогает. data.sort() меняет список на месте и возвращает
None. - key=lambda x: ... говорит, по какой части каждого элемента сравнивать — индекс кортежа, ключ словаря или что угодно ещё.
- reverse=True разворачивает готовый порядок сортировки на противоположный, ничего не меняя в самом сравнении.
- Если
keyвозвращает кортеж, Python сравнивает его по элементам по очереди — так получается сортировка сразу по нескольким признакам. - enumerate(seq, start=1) в цикле
for i, x in ...отдаёт номер и значение вместе, без ручного счётчика. - zip(a, b) соединяет два списка по позиции в пары; если списки разной длины, лишние элементы длинного списка отбрасываются.
Проверьте себя
6 вопросов
Лямбда и sorted
PremiumСортировка по ключу
Дан список кортежей: students = [("Alice", 85), ("Bob", 72), ("Charlie", 90)] — в каждом кортеже имя и оценка.
Напишите функцию sort_by_score(students), которая возвращает список этих же кортежей, отсортированный по оценке (второй элемент кортежа) в порядке убывания. Используйте sorted() с параметрами key и reverse.
Например, для списка выше функция должна вернуть [("Charlie", 90), ("Alice", 85), ("Bob", 72)].