Как отсортировать список в Python: все способы от простого к продвинутому 🔢

Сортировка — одна из самых частых задач в программировании. Неважно, пишешь ли ты Telegram-бота, парсишь данные или делаешь аналитический отчёт — рано или поздно тебе понадобится упорядочить элементы списка. И тут новички начинают гуглить, путать sort() и sorted(), ломают голову над лямбда-функциями и наступают на одни и те же грабли.

Эта статья — полный разбор того, как отсортировать список в Питоне. От базовой сортировки чисел и строк до сложных случаев с вложенными структурами, пользовательскими ключами и обратным порядком. Без воды, с примерами кода, которые можно скопировать и сразу запустить.

Если ты только учишь Python или уже пишешь проекты, но каждый раз забываешь, какой метод сортировки использовать — сохрани эту статью в закладки. Здесь разобрано всё, что нужно.

Базовая сортировка списка: sort() и sorted() — в чём разница 🔄

В Python есть два способа отсортировать список, и новички постоянно их путают. Разница принципиальная: один меняет исходный список, а второй создаёт новый. Запомни это — и половина ошибок уйдёт сама.

Метод sort() — сортирует список на месте. Он изменяет тот список, к которому применяется, и ничего не возвращает (точнее, возвращает None). Используй его, когда исходный порядок не нужен и ты хочешь сэкономить память.

Функция sorted() — создаёт новый отсортированный список. Оригинальный список остаётся нетронутым. Применяй, когда нужно сохранить первоначальные данные.

# Условие: есть список чисел, нужно отсортировать двумя способами

# Способ 1: sort() — меняет исходный список
numbers = [5, 2, 8, 1, 9, 3]
numbers.sort()
print(numbers)  # [1, 2, 3, 5, 8, 9]

# Способ 2: sorted() — создаёт новый список
numbers = [5, 2, 8, 1, 9, 3]
result = sorted(numbers)
print(result)    # [1, 2, 3, 5, 8, 9]
print(numbers)   # [5, 2, 8, 1, 9, 3] — оригинал не изменился!

Разбор по строкам:

  • numbers.sort() — вызывает метод sort() у списка numbers. Список сортируется на месте, метод возвращает None.
  • sorted(numbers) — передаёт список в функцию sorted(), которая возвращает новый список. Оригинал numbers не трогается.
  • print(numbers) во втором случае показывает, что порядок остался прежним — это и есть ключевое отличие.

Частая ошибка новичка:

# ОШИБКА: пытаются сохранить результат sort()
numbers = [5, 2, 8, 1]
sorted_numbers = numbers.sort()
print(sorted_numbers)  # None!

sort() возвращает None, поэтому в sorted_numbers лежит None, а не отсортированный список. Если нужно сохранить результат в переменную — используй sorted().

Совет: запомни простое правило. Нужно изменить оригинальный список — sort(). Нужно получить отсортированную копию — sorted(). Третьего не дано.

Сортировка в обратном порядке и по разным типам данных ⬇️

По умолчанию sort() и sorted() сортируют по возрастанию. Но часто нужно обратное — от большего к меньшему. И тут оба способа поддерживают параметр reverse.

Обратная сортировка

# Условие: отсортировать список оценок от высшей к низшей

grades = [3, 5, 4, 2, 5, 4, 3]
grades.sort(reverse=True)
print(grades)  # [5, 5, 4, 4, 3, 3, 2]

Разбор:

  • reverse=True — параметр, который меняет направление сортировки на убывание.
  • reverse=False — значение по умолчанию, сортировка по возрастанию.

Сортировка строк

Строки в Python сортируются лексикографически — по кодам символов, как в словаре. Но есть нюанс с регистром.

# Сортировка строк по алфавиту
fruits = ["банан", "яблоко", "вишня", "апельсин"]
print(sorted(fruits))  # ['апельсин', 'банан', 'вишня', 'яблоко']

# Сортировка строк разного регистра
words = ["банан", "Яблоко", "вишня", "Апельсин"]
print(sorted(words))
# ['Апельсин', 'Яблоко', 'банан', 'вишня']
# Прописные буквы идут раньше строчных!

Почему «Апельсин» и «Яблоко» оказались в начале? Заглавные буквы имеют меньшие коды в Unicode, чем строчные. Чтобы игнорировать регистр, используй параметр key:

words = ["банан", "Яблоко", "вишня", "Апельсин"]
print(sorted(words, key=str.lower))
# ['Апельсин', 'банан', 'вишня', 'Яблоко']

Разбор:

  • key=str.lower — передаёт функцию str.lower как ключ сортировки. Каждое слово приводится к нижнему регистру перед сравнением, но сам результат остаётся с оригинальным регистром.

Сортировка чисел как строк

Ещё один подвох: если числа хранятся в виде строк, сортировка будет лексикографической, а не числовой.

numbers_str = ["10", "2", "1", "20", "100"]
print(sorted(numbers_str))
# ['1', '10', '100', '2', '20'] — не числовой порядок!

print(sorted(numbers_str, key=int))
# ['1', '2', '10', '20', '100'] — теперь правильно

key=int преобразует каждую строку в число перед сравнением. Сами элементы остаются строками, но порядок — числовой.

Совет: всегда проверяй тип данных перед сортировкой. Строки сортируются как текст, числа — как числа. Смешанные типы в одном списке вызовут TypeError.

Параметр key: сортировка по кастомному правилу 🎯

Параметр key — самый мощный инструмент сортировки в Python. Он принимает функцию, которая применяется к каждому элементу, и результат функции используется для сравнения. Сам элемент при этом не меняется.

Сортировка по длине строки

# Условие: отсортировать слова по длине, от коротких к длинным

words = ["информатика", "код", "программа", "Питон", "ИИ"]
result = sorted(words, key=len)
print(result)
# ['ИИ', 'код', 'Питон', 'программа', 'информатика']

Разбор:

  • key=len — встроенная функция len() применяется к каждому элементу. Слова сравниваются по длине, а не по алфавиту.

Сортировка словарей в списке

Это классическая задача: у тебя есть список словарей, и нужно отсортировать по значению одного из ключей.

# Условие: список студентов с оценками, отсортировать по убыванию балла

students = [
    {"name": "Анна", "score": 88},
    {"name": "Борис", "score": 95},
    {"name": "Вера", "score": 72},
    {"name": "Глеб", "score": 95},
]

# Сортировка по баллу (по убыванию)
result = sorted(students, key=lambda x: x["score"], reverse=True)

for s in result:
    print(f"{s['name']}: {s['score']}")
# Борис: 95
# Глеб: 95
# Анна: 88
# Вера: 72

Разбор по строкам:

  • lambda x: x["score"] — анонимная функция, которая берёт словарь x и возвращает значение по ключу "score". Именно это значение используется для сравнения.
  • reverse=True — сортировка по убыванию, от максимума к минимуму.
  • Борис и Глеб оба набрали 95. Python сохраняет их исходный порядок — это называется стабильная сортировка. Элементы с равными ключами не меняют позиций относительно друг друга.

Сортировка по нескольким критериям

Что если два студента набрали одинаковый балл и нужно отсортировать их по имени? Для этого передай key в виде кортежа.

# Условие: сортируем по баллу (убывание), при равенстве — по имени (возрастание)

students = [
    {"name": "Борис", "score": 95},
    {"name": "Анна", "score": 88},
    {"name": "Глеб", "score": 95},
    {"name": "Дарья", "score": 88},
]

result = sorted(students, key=lambda x: (-x["score"], x["name"]))
for s in result:
    print(f"{s['name']}: {s['score']}")
# Борис: 95
# Глеб: 95
# Анна: 88
# Дарья: 88

Разбор:

  • (-x["score"], x["name"]) — кортеж из двух элементов. Минус перед "score" даёт сортировку по убыванию для чисел (больше число → меньше значение с минусом → раньше в списке). Имя сортируется по возрастанию.
  • Для строк минус не работает, поэтому если нужно сортировать строку по убыванию, используй отдельный sorted с reverse или модуль operator.

Модуль operator — замена лямбдам

Лямбды удобны, но для частых случаев есть готовые функции в модуле operator:

from operator import itemgetter, attrgetter

# itemgetter — для словарей и кортежей
students = [("Анна", 88), ("Борис", 95), ("Вера", 72)]
result = sorted(students, key=itemgetter(1), reverse=True)
print(result)  # [('Борис', 95), ('Анна', 88), ('Вера', 72)]

# Сортировка по двум полям
result = sorted(students, key=itemgetter(1, 0))
print(result)  # [('Вера', 72), ('Анна', 88), ('Борис', 95)]

itemgetter(1) возвращает функцию, которая извлекает элемент с индексом 1. Аналогично itemgetter(1, 0) извлекает кортеж (элемент_1, элемент_0) — сортировка по двум полям сразу.

Совет: если key-функция становится длинной и запутанной, вынеси её в отдельную def-функцию. Лямбды хороши для одной строки, не больше.

Сложные случаи: вложенные списки, кортежи и объекты 🧩

В реальных проектах данные редко лежат в простом списке чисел. Чаще ты работаешь со списками кортежей, вложенными структурами или объектами классов. Принцип тот же — определяешь key и сортируешь.

Сортировка списка кортежей

# Условие: список координат точек, отсортировать по удалённости от нуля

points = [(3, 4), (1, 1), (0, 2), (5, 12), (8, 6)]

result = sorted(points, key=lambda p: (p[0]**2 + p[1]**2) ** 0.5)
print(result)
# [(1, 1), (0, 2), (3, 4), (8, 6), (5, 12)]

Разбор:

  • lambda p: (p[0]**2 + p[1]**2) ** 0.5 — вычисляет расстояние от точки до начала координат по теореме Пифагора. Сортировка идёт по этому расстоянию, но сами точки остаются нетронутыми.

Сортировка объектов классов

Если у тебя есть список объектов и нужно отсортировать по атрибуту, используем attrgetter:

from operator import attrgetter

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def __repr__(self):
        return f"{self.name}({self.score})"

students = [
    Student("Анна", 88),
    Student("Борис", 95),
    Student("Вера", 72),
]

result = sorted(students, key=attrgetter("score"), reverse=True)
print(result)  # [Борис(95), Анна(88), Вера(72)]

Разбор:

  • attrgetter("score") — извлекает атрибут score из каждого объекта. Аналог lambda x: x.score, но компактнее.
  • __repr__ нужен, чтобы при print объект выводился читаемо, а не как <__main__.Student object at 0x...>.

Сортировка с cmp_to_key — старый стиль

В Python 2 был параметр cmp, который принимал функцию-компаратор (возвращала -1, 0 или 1). В Python 3 его убрали, но если нужно использовать старую логику, есть мост cmp_to_key из модуля functools:

from functools import cmp_to_key

# Условие: сортировка чисел по количеству делителей
def count_divisors(n):
    count = 0
    for i in range(1, n + 1):
        if n % i == 0:
            count += 1
    return count

numbers = [15, 6, 10, 12, 7]

# Через key — проще и быстрее
result = sorted(numbers, key=count_divisors)
print(result)  # [7, 6, 10, 15, 12]

# Через cmp_to_key — если нужна сложная логика сравнения
def compare(a, b):
    da, db = count_divisors(a), count_divisors(b)
    if da < db:
        return -1
    elif da > db:
        return 1
    else:
        return 0

result = sorted(numbers, key=cmp_to_key(compare))
print(result)  # [7, 6, 10, 15, 12]

В 95% случаев key справляется лучше и быстрее, чем cmp_to_key. Используй компаратор, только если логика сравнения не сводится к вычислению одного значения.

Совет: для сложных структур данных сначала попробуй выразить критерий сортировки через key-функцию. Если не получается — тогда cmp_to_key. Но чаще всего первый вариант работает.

Подводные камни и типичные ошибки при сортировке ⚠️

Разберём ошибки, на которые спотыкаются новички. Некоторые из них приводят к неправильному результату без сообщения об ошибке — и это самое коварное.

1. Смешанные типы в списке

data = [5, "3", 2, "10", 1]
print(sorted(data))  # TypeError: '<' not supported between 'int' and 'str'

Python не может сравнить число и строку. Решение — привести всё к одному типу:

data = [5, "3", 2, "10", 1]
print(sorted(data, key=int))  # [1, 2, 5, '10', '3']

Здесь key=int работает: строки "3" и "10" преобразуются в числа для сравнения. Но результат содержит смешанные типы — оригинальные элементы не меняются.

2. Изменение списка во время итерации

# ПЛОХО: меняешь список, по которому итерируешься
numbers = [3, 1, 4, 1, 5]
for i in range(len(numbers)):
    numbers.sort()
    print(numbers[i])
# Результат непредсказуем, потому что сортировка сдвигает элементы

Решение: сортируй до цикла, а не внутри:

numbers = [3, 1, 4, 1, 5]
numbers.sort()
for n in numbers:
    print(n)  # 1, 1, 3, 4, 5

3. Ожидание, что sort() вернёт список

Мы уже разбирали это, но повторю, потому что ошибка встречается постоянно:

result = [3, 1, 4].sort()
print(result)  # None

sort() всегда возвращает None. Нужно — используй sorted().

4. Сортировка множества или словаря напрямую

my_set = {5, 2, 8, 1}
my_set.sort()  # AttributeError: 'set' object has no attribute 'sort'

У множеств нет порядка, поэтому метода sort() у них нет. Но sorted() работает с любым итерируемым объектом:

my_set = {5, 2, 8, 1}
result = sorted(my_set)
print(result)  # [1, 2, 5, 8]

Обрати внимание: результат — список, а не множество. sorted() всегда возвращает список, независимо от типа входных данных.

5. Неправильная сортировка с None в данных

data = [5, None, 2, None, 8]
print(sorted(data))  # TypeError: '<' not supported between 'NoneType' and 'int'

Решение: обработай None в key-функции:

data = [5, None, 2, None, 8]
result = sorted(data, key=lambda x: (x is None, x))
print(result)  # [2, 5, 8, None, None]

Разбор: x is None возвращает True (1) или False (0) для каждого элемента. None-значения оказываются в конце, потому что True > False. Если нужно None в начале — поменяй местами: key=lambda x: (x is not None, x).

Совет: если в данных могут быть None, всегда обрабатывай их в key-функции. Python не умеет сравнивать None с числами и строками — получишь TypeError.

Производительность сортировки и алгоритм Timsort 🚀

Python использует алгоритм Timsort — гибрид сортировки слиянием и сортировки вставками. Он разработан Тимом Питерсом в 2002 году специально для Python и оптимизирован для реальных данных, которые часто содержат уже отсортированные фрагменты.

Характеристики Timsort:

  • Время в худшем случае: $O(n \log n)$
  • В лучшем случае (данные уже отсортированы): $O(n)$
  • Стабильность: сортировка стабильна — элементы с равными ключами сохраняют исходный порядок
  • Доп. память: $O(n)$

Это значит, что встроенная сортировка Python достаточно быстрая для большинства задач. Но есть нюансы:

Сложность key-функции влияет на скорость. Если key-функция вызывает дорогие вычисления, Python применяет её к каждому элементу на каждом сравнении. Точнее, Python вызывает её один раз на элемент и кэширует результат — это называется декорирование-сортировка-декорирование (decorate-sort-undecorate). Но всё равно, чем проще key, тем быстрее работает сортировка.

import time

data = list(range(100000, 0, -1))

# Быстрый key
start = time.time()
result = sorted(data)
print(f"Без key: {time.time() - start:.4f} сек")

# Медленный key — вычисление для каждого элемента
start = time.time()
result = sorted(data, key=lambda x: sum(int(d) for d in str(x)))
print(f"Сложный key: {time.time() - start:.4f} сек")

На 100 000 элементов разница может быть в десятки раз. Простой key=len или key=int почти не замедляет сортировку, а вычисление факториала для каждого элемента — сильно.

Когда стоит задуматься о производительности:

  • Список больше 100 000 элементов
  • key-функция делает сложные вычисления
  • Сортировка вызывается в цикле много раз

В этих случаях имеет смысл предварительно вычислить ключи и сохранить их, или использовать numpy для числовых данных.

Совет: не оптимизируй сортировку заранее. Встроенные sort() и sorted() работают достаточно быстро для 99% задач. Оптимизация нужна только там, где профайлер показал реальное узкое место.

10 часто задаваемых вопросов

  1. Чем отличаются sort() и sorted() в Python? sort() сортирует список на месте и возвращает None. sorted() создаёт новый отсортированный список и не меняет оригинал.
  1. Как отсортировать список по убыванию? Передай параметр reverse=True: sorted(data, reverse=True) или data.sort(reverse=True).
  1. Как отсортировать список словарей по значению ключа? Используй key с лямбдой: sorted(data, key=lambda x: x["ключ"]).
  1. Как отсортировать список строк без учёта регистра? Передай key=str.lower в sort() или sorted().
  1. Можно ли отсортировать множество (set) в Python? Множества не имеют порядка, поэтому sort() у них нет. Но sorted() работает и вернёт отсортированный список.
  1. Как отсортировать список кортежей по второму элементу? sorted(data, key=lambda x: x[1]) или sorted(data, key=itemgetter(1)).
  1. Что такое стабильная сортировка? Это сортировка, при которой элементы с равными ключами сохраняют свой изначальный порядок. Сортировка в Python — стабильная.
  1. Как отсортировать список, в котором есть None? Обработай None в key-функции: key=lambda x: (x is None, x) — поставит None в конец.
  1. Какой алгоритм сортировки использует Python? Timsort — гибрид сортировки слиянием и вставками. Сложность $O(n \log n)$, в лучшем случае $O(n)$.
  1. Как отсортировать список сразу по двум критериям? Верни кортеж из key-функции: key=lambda x: (x["score"], x["name"]) — сортировка по баллу, при равенстве по имени.

Сортировка списков в Python — тема, которая кажется простой, пока не столкнёшься с реальными данными. Но если ты разобрался с key-функциями, лямбдами и знаешь разницу между sort() и sorted(), то 90% задач закроешь без гугления.

Хочешь узнать, какой курс обучения Python подходит именно тебе? Пройди опрос:

Сайт: https://infazy.ru/podobrat-kurs-python
Телеграмм бот: https://t.me/kursypythonbot

#Python #СортировкаСпискаPython #СортировкаПитон #PythonДляНовичков #СпискиPython #ФункцияSorted #МетодSort #КлючСортировкиPython #ЛямбдаПитон #ПрограммированиеНаПитоне #PythonУроки #ОбучениеПрограммированию

4 views