Сортировка и топ-N: sorted по ключу
Список из API приходит в том порядке, в каком отдал сервер: sorted с key наводит порядок, reverse разворачивает, срез берёт топ-3.
Редакция Питоники
Фильтр из прошлого урока отобрал подходящие товары — но порядок в выборке остался таким, каким его отдал сервер. А людям подавай порядок: каталог — по возрастанию цены, витрина — по убыванию рейтинга, отчёт — с топ-3 во главе. За всё это отвечает одна стандартная функция — sorted, и в этом уроке ты поставишь её на поток.
Всё, как обычно в разделе, исполняется прямо на странице: сохранённый ответ API лежит в константе, sorted обрабатывает его без всякой сети.
sorted по ключу: сортируем по полю
sorted принимает список и параметр key — функцию, которая для каждого элемента говорит, с чем именно сравнивать. Для списка словарей это почти всегда lambda, достающая поле.
import json
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
by_price = sorted(items, key=lambda item: item["price"])
for item in by_price:
print(item["name"], "-", item["price"])
Мышь - 1290 Веб-камера - 2450 Клавиатура - 4990 Наушники - 7350 Монитор - 18400
Разбираем запись. lambda item: item["price"] — маленькая безымянная функция: получает словарь, возвращает цену. sorted вызывает её для каждого элемента и расставляет элементы по этим значениям: число вызовов равно числу элементов, а сравнивает она уже готовые значения. Исходный items не тронут — sorted построила новый список, точно как фильтр в прошлом уроке строил выборку.
Lambda — не единственный вариант: key принимает любую функцию, которая умеет превращать элемент в значение для сравнения. Если правило сложнее одного обращения к полю — выноси его в обычную функцию с именем: читаемость дороже экономии строки.
def by_price_then_rating(item):
if item["in_stock"]:
return (0, item["price"], -item["rating"])
return (1, item["price"], -item["rating"])
# items - список словарей товаров из ответа API
# top = sorted(items, key=by_price_then_rating)[:3]
import json
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
top_prices = sorted(items, key=lambda item: item["price"], reverse=True)
for item in top_prices:
print(item["name"], "-", item["price"])
Монитор - 18400 Наушники - 7350 Клавиатура - 4990 Веб-камера - 2450 Мышь - 1290
reverse=True переворачивает порядок: от дорогого к дешёвому. Тот же параметр работает и для рейтинга — витрина «от лучшего к худшему» пишется одной строкой. Направление — это часть вопроса, а не отдельный навык: какой порядок делает отчёт понятнее, такой и выбирай. Списки-числа, кстати, сортируются без всякого key: sorted([3, 1, 2]) — уже [1, 2, 3]; key нужен там, где элементы сложные.
Топ-3: срез после сортировки
Топ-N — это сортировка по убыванию плюс срез: сначала расставили по местам, потом взяли первые N. Сортировка не угадывает, что тебе нужно: без key она честно сравнивает словари целиком и падает с ошибкой. Поэтому key здесь обязателен.
import json
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
top3 = sorted(items, key=lambda item: item["price"], reverse=True)[:3]
for place, item in enumerate(top3, start=1):
print(place, "-", item["name"], "-", item["price"], "руб.")
1 - Монитор - 18400 руб. 2 - Наушники - 7350 руб. 3 - Клавиатура - 4990 руб.
Срез [:3] забрал первые три элемента уже отсортированного списка — топ-5 пишется как [:5], топ-10 как [:10]. enumerate с start=1 пронумеровал места с единицы, а не с нуля: в отчёте первое место должно быть первым. Анти-топ считается зеркально: без reverse сортировка по возрастанию ставит дешёвых в начало, и тот же срез [:3] собрал бы «три самых доступных» — направление задавать до среза, а не после.
items = [
{"name": "Мышь", "price": 1290},
{"name": "Коврик", "price": 890},
]
sorted(items)
# TypeError: '<' not supported between instances of 'dict' and 'dict'
Строки и регистр: куда девается кириллица
Сортировать можно и по строковым полям — по названию, например. Но строки сравниваются по кодам символов, а в Unicode все заглавные буквы стоят раньше строчных: К с кодом 1050, строчное к — с кодом 1082. Отсюда сюрприз: слова с большой буквы оказываются впереди всех слов с маленькой, даже если по алфавиту должны быть в конце.
names = ["монитор", "Клавиатура", "Мышь", "наушники"]
print(sorted(names))
print(sorted(names, key=str.lower))
['Клавиатура', 'Мышь', 'монитор', 'наушники'] ['Клавиатура', 'монитор', 'Мышь', 'наушники']
Первая сортировка поставила оба слова с маленькой буквы в конец — по кодам всё честно, по алфавиту нет. key=str.lower сравнивает элементы в нижнем регистре: сортировка становится человеческой, а исходные строки в списке остаются как были — key меняет только способ сравнения, не сами данные. Приём универсален для любых строковых полей: названия, авторы, города — везде сравнивай в нижнем регистре, храни как есть.
Тот же приём работает и при сортировке товаров по названию — key комбинирует поле и lower: sorted(items, key=lambda item: item["name"].lower()). И ещё одна ловушка ждёт там, где цены приехали строками: строки сравниваются посимвольно, и «18400» окажется меньше «2450», потому что единица идёт раньше двойки. Числам — численное сравнение, строкам — лексикографическое.
raw = [
{"name": "Монитор", "price": "18400"},
{"name": "Веб-камера", "price": "2450"},
]
by_text = sorted(raw, key=lambda item: item["price"])
by_number = sorted(raw, key=lambda item: float(item["price"]))
print([i["price"] for i in by_text])
print([i["price"] for i in by_number])
['18400', '2450'] ['2450', '18400']
Первая строка отсортировала строки как словарь для кроссворда: по первым символам. Вторая сначала превратила цены в числа — и порядок стал настоящим. Вывод для практики: перед сортировкой приведи поля к нужным типам, как перед сравнением в фильтре из урока 15 — это одна и та же граница «текст-данные».
Стабильность: кто первый при равных
Что будет с элементами с одинаковым ключом — два товара по одной цене, два заказа с одним сроком? Ничего страшного: сортировка в Python стабильна, при равных ключах она сохраняет исходный порядок. Кто раньше стоял в списке — тот и раньше оказался в отсортированном, а порядок, в котором сервер прислал товары, обычно и есть порядок витрины.
orders = [
{"id": 101, "days": 3},
{"id": 102, "days": 1},
{"id": 103, "days": 3},
]
by_age = sorted(orders, key=lambda order: order["days"])
print([order["id"] for order in by_age])
[102, 101, 103]
Заказы 101 и 103 простаивали одинаково — и остались в исходном порядке: 101 перед 103. Стабильность делает возможным приём «сортировка по двум полям»: сначала отсортировать по вторичному ключу, потом по основному — равные по основному сохранят порядок вторичного.
Есть путь и короче: key может возвращать кортеж — тогда сравнение идёт по элементам кортежа слева направо, и обе сортировки происходят за один проход. Хитрость с булевым полем: not item["in_stock"] даёт False (ноль) для товаров в наличии, поэтому они оказываются в начале списка.
import json
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
by_stock_price = sorted(
items,
key=lambda item: (not item["in_stock"], item["price"]),
)
for item in by_stock_price:
print(item["name"], "-", item["price"], "-", item["in_stock"])
Мышь - 1290 - True Клавиатура - 4990 - True Наушники - 7350 - True Веб-камера - 2450 - False Монитор - 18400 - False
Сначала все товары в наличии по возрастанию цены, следом недоступные — точно так, как сортирует карточки настоящий магазин. Кортеж в key — самый экономный способ выразить «сначала по этому, потом по тому»: не два вызова sorted, а один с парой критериев. Ловушка одна: элементы кортежа должны быть сравнимы между собой — числа с числами, строки со строками; смешивать типы в одном элементе кортежа нельзя.
sorted против sort
У списков есть и метод sort() — сортирует на месте. Разница в двух вещах: sorted возвращает новый список, sort() меняет существующий и возвращает None.
nums = [3, 1, 2]
copy = sorted(nums)
print("sorted вернул:", copy)
print("исходный цел:", nums)
nums.sort()
print("после sort():", nums)
print("sort вернул:", nums.sort())
sorted вернул: [1, 2, 3] исходный цел: [3, 1, 2] после sort(): [1, 2, 3] sort вернул: None
Для данных из API выбирай sorted: ответ приходит один раз, а вопросов к нему бывает много — у каждого свой отсортированный список, и никто не портит чужой. Метод sort() — для больших списков, когда копия не нужна. И запомни на собеседование: sorted работает с любым итерируемым — кортежем, генератором, — а sort() есть только у списков.
| Хочется | Запись |
|---|---|
| по возрастанию поля | sorted(items, key=lambda i: i["price"]) |
| по убыванию | то же самое + reverse=True |
| топ-3 | sorted(..., reverse=True)[:3] |
| человеческий алфавит | sorted(names, key=str.lower) |
| изменить список на месте | items.sort(key=...) — вернёт None |
Финал: фильтр плюс сортировка
import json
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
available = [item for item in items if item["in_stock"]]
by_price = sorted(available, key=lambda item: item["price"])
for item in by_price:
print(item["name"], "-", item["price"])
Мышь - 1290 Клавиатура - 4990 Наушники - 7350
Две строки — две операции из прошлых уроков: фильтр отобрал доступное, sorted навёл порядок. Именно из таких шагов собирается любой пайплайн обработки ответа API, и финальный проект урока 20 будет именно этим приёмом в масштабе. Обрати внимание и на экономию: сортировать пять товаров или тридцать тысяч — запись не меняется, а срез [:3] на большом списке не станет медленнее — sorted всё равно пройдёт по списку один раз.
Что дальше
Порядок наведён: по цене, по рейтингу, с топами и человеческим алфавитом — и все варианты выросли из одной функции с параметром key. Осталось последнее — результат живёт только до конца скрипта, а отчёт положено сохранять. Следующий урок записывает итоговые данные в json-файл с ensure_ascii=False и indent=2 — и проверяет себя обратным чтением. А про сортировку уже после получения ответа из сети рассказывает финальный проект курса requests.
Топ-N — это не отдельная функция, а привычка: sorted с key ставит элементы по местам, срез забирает нужное количество.
Сначала предскажи ответ в голове — это главный навык программиста.
nums = [3, 1, 2]
copy = sorted(nums)
print(nums, copy)
words = ["банан", "Арбуз", "вишня"]
print(sorted(words, key=str.lower))
data = [{"n": "x", "v": 2}, {"n": "y", "v": 1}]
print(sorted(data, key=lambda d: d["v"])[0]["n"])
1. Что делает sorted(items, key=lambda item: item["price"]) с исходным списком?
2. Как отсортировать товары от дорогого к дешёвому?
3. Зачем в sorted нужен key для списка словарей?
4. Как из отсортированного по убыванию списка взять топ-3?
5. Почему sorted(names) поставила «Мышь» раньше «монитор»?
6. Что произойдёт с двумя товарами одинаковой цены при сортировке?
Собери витрину «выбор редакции»: отсортируй товары из сохранённого ответа по рейтингу от высокого к низкому, возьми первые три и выведи каждое место строкой вида «1 - Монитор - 4.8».
Как взять три самых дешёвых товара?
Сортировать по возрастанию и взять срез: sorted(items, key=lambda i: i["price"])[:3]. Никакого reverse не нужно — возрастание уже ставит дешёвых в начало, и срез забирает первых трёх. Проверяй направление до среза: срез после неправильного направления отдаст не тех, а ошибка не поднимет ни флага.
Как отсортировать список словарей по значению поля в Python?
Функцией sorted с параметром key: sorted(items, key=lambda item: item["price"]). key получает один словарь и возвращает поле для сравнения. Для убывания добавьте reverse=True. Исходный список не меняется — sorted возвращает новый.
Как взять топ-3 самых дорогих товаров из данных API?
Отсортируйте по цене по убыванию и возьмите срез: sorted(items, key=lambda i: i["price"], reverse=True)[:3]. Срез применяется после сортировки, поэтому первые три элемента — действительно лидеры. Для топ-10 срез меняется на [:10].
Почему Python сортирует русские строки не по алфавиту?
Потому что сравнивает коды символов, а заглавные кириллические буквы в Unicode идут раньше строчных: «Мышь» обгоняет «монитор». Лечится параметром key=str.lower — сравнение идёт в нижнем регистре, а исходные строки не меняются.
Чем sorted отличается от list.sort?
sorted(x) — стандартная функция: возвращает новый отсортированный список, не трогая исходный. x.sort() — метод списка: сортирует на месте и возвращает None. Для обработки данных из API удобнее sorted: один ответ, много разных сортировок без побочных эффектов.
Как отсортировать сначала по наличию, потом по цене?
Передать в key кортеж: sorted(items, key=lambda i: (not i["in_stock"], i["price"])). Элементы сравниваются по кортежу слева направо: False (ноль) товаров в наличии оказывается раньше, а внутри групп работает цена. Альтернатива — два последовательных sorted от частного ключа к общему: сработает благодаря стабильности сортировки.
Понравился урок? Сошлитесь на него
«Сортировка не угадывает, что тебе нужно: без key она честно сравнивает словари целиком и падает с ошибкой.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
json · Урок 10
Обход данных: циклы по JSON-спискам
for по списку словарей, извлечение полей, суммирование выручки и list comprehension — превращаем выгрузку заказов в отчёт.
json · Урок 15
Фильтрация ответа API: цены, рейтинги, флаги
Товары из ответа API приехали — теперь вопросы: что дешевле N, что в наличии, какая средняя цена и сколько процентов подходит под условие.
json · Урок 17
Сохраняем результаты: json.dump итоговых данных
Отфильтровали, отсортировали, посчитали — теперь результат должен пережить скрипт: json.dump пишет итог в файл с кириллицей и лесенкой.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
BeautifulSoup / Scrapy · Урок 5
Парсинг таблиц и списков: собираем данные в структуру
Таблица — самая частая структура в вебе: курсы валют, расписания, прайсы. Собираем thead и tbody в список словарей, чистим цены и разбираем colspan.
список словарей pythonпарсинг данных с сайта в excel
BeautifulSoup / Scrapy · Урок 10
Проект: парсер каталога товаров с ценами и сохранением в CSV
Финальный проект раздела: из приёмов девяти уроков собираем один работающий парсер каталога — обход страниц, чистка цен, CSV и отчёт по средним ценам.
список словарей в csvпарсинг данных для портфолио
json · Урок 11
Фильтрация данных: найти нужное в JSON
Включение с условием для отбора, next с default для первого подходящего, any и all для вопросов ко всей коллекции — работаем с выгрузкой заказов.
python фильтрация json данныхpython отфильтровать список словарей