Структуры данных в Python: list, dict, set, tuple
Структуры данных Python - ежедневка любой роли, и вопросы тут приземлённые: «почему set быстрее списка», «что не так с дефолтом x=[]». Проверяют, чувствуешь ли ты цену операций за синтаксическим сахаром.
// Половина «медленного питона» на код-ревью это x in list внутри цикла. Начни с этого.
list и deque: что почём
list - динамический массив: доступ по индексу и append в конец - O(1) амортизированно, вставка или удаление в начале - O(n), весь хвост сдвигается. Очередь на списке - квадратичная боль; для неё deque с O(1) на обоих концах.
Comprehensions - идиома и скорость: [f(x) for x in xs] быстрее цикла с append; генераторное выражение - то же, но лениво, без материализации списка.
// list.sort() сортирует на месте и возвращает None: a = a.sort() - потерянные данные. Новый список отдаёт sorted(a).
- амортизированный O(1)
- редкие дорогие переаллокации размазаны по серии дешёвых операций
dict и set: хеш-таблицы под капотом
dict и set - хеш-таблицы: доступ, вставка и проверка вхождения - O(1) в среднем. Отсюда главная оптимизация языка: x in list это O(n) перебор, x in set - O(1); замена списка на множество в цикле поиска превращает квадрат в линию.
Ключи dict и элементы set обязаны быть хешируемыми, то есть иммутабельными: tuple и str годятся, list и dict - нет. Иммутабельность неглубокая: кортеж со списком внутри нехешируем.
// dict с Python 3.7 сохраняет порядок вставки это гарантия языка, а не деталь реализации. set порядка не гарантирует никакого.
- хешируемость
- у объекта стабильный __hash__; условие для ключей dict и элементов set
Копии, ссылки и мутабельный дефолт
Присваивание не копирует: b = a - вторая ссылка на тот же список, правка b видна в a. copy() - поверхностная копия (вложенные объекты общие), deepcopy - рекурсивная.
Знаменитая ловушка - мутабельный дефолт аргумента: он вычисляется один раз при определении функции и живёт между вызовами:
def f(x=[]):
x.append(1)
return x
f() # [1]
f() # [1, 1] — список общий между вызовами
def g(x=None):
x = [] if x is None else x # правильный паттерн- поверхностная копия
- новый контейнер, но вложенные объекты - те же ссылки
Как отвечать: «Что вернут два вызова функции с дефолтом x=[]?»
Первый - [1], второй - [1, 1]: дефолтное значение вычисляется один раз при определении функции и живёт в её объекте, так что все вызовы без аргумента делят один список. Правильный паттерн - x=None и создание списка внутри: x = [] if x is None else x. Та же ловушка прячется в dataclass с default=[] - там спасает field(default_factory=list). В живом коде мутабельных дефолтов в сигнатурах не бывает.
Точный вывод, механизм, канонический фикс и перенос на dataclass - вопрос закрыт со всеми его продолжениями.
На чём валят
- −x in list внутри цикла - O(n²) на ровном месте; set решает.
- −def f(items=[]) - список общий для всех вызовов, данные «протекают» между ними.
- −b = a для списка - не копия: правка b видна в a.
- −a = a.sort() - sort() возвращает None, данные потеряны; новый список - sorted(a).
- −Мутировать список во время итерации по нему - пропущенные элементы.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 19, остальные разбираются в тренажёре.
- Когда осмысленно взять tuple вместо list?A)Кортеж в большинстве ситуаций быстрее и удобнее списка, поэтому список можно почти не использоватьB)Список превосходит кортеж по большинству параметров, а кортеж оставили для обратной совместимостиC)Когда набор фиксирован и не должен меняться: запись/координаты, ключ словаря, защита от случайной мутации при передачеD)Когда элементов ровно два, потому что кортеж не способен хранить больше двух элементов
показать ответ и разбор
+C)Когда набор фиксирован и не должен меняться: запись/координаты, ключ словаря, защита от случайной мутации при передаче// разбор: Кортеж берут для неизменяемой упорядоченной записи: фиксированные поля (координаты, строка результата), ключ словаря или элемент множества (кортеж хешируем), защита данных от случайной мутации при передаче в функцию. Список — когда коллекция должна расти и меняться. Число элементов у кортежа не ограничено.
- Что дают collections.defaultdict и Counter по сравнению с обычным dict?A)Они заменяют обычный dict в большинстве задач и при этом работают в разы быстрее егоB)Они автоматически сохраняют свои данные на диск между запусками программы, в отличие от обычного словаря, живущего в памятиC)Они сортируют ключи по алфавиту при каждой вставке, чего обычный неупорядоченный словарь делать не умеетD)defaultdict создаёт значение по умолчанию для нового ключа (нет KeyError), а Counter считает частоты элементов одной строкой
показать ответ и разбор
+D)defaultdict создаёт значение по умолчанию для нового ключа (нет KeyError), а Counter считает частоты элементов одной строкой// разбор: defaultdict(list или int) сам создаёт значение по умолчанию при первом обращении к новому ключу — не нужно проверять «есть ли ключ» перед добавлением, удобно для группировки и накопления. Counter принимает итерируемое и считает частоты, а most_common(k) отдаёт топ. Оба — обычные словари с удобным поведением, не быстрее и не персистентные.
- Что должно быть верно у объекта, чтобы его можно было положить в set или сделать ключом dict?A)Объект обязан быть числом или строкой — произвольные пользовательские объекты в set и ключи dict класть не получитсяB)Объект должен занимать не более фиксированного числа байт, иначе хеш-таблица не сможет корректно его у себя разместитьC)Он должен быть хешируемым: иметь стабильный __hash__, согласованный с __eq__ — равные объекты обязаны иметь равный хешD)Достаточно, чтобы объект был изменяемым — тогда set сможет обновлять его на месте при коллизиях хешей заметно быстрее
показать ответ и разбор
+C)Он должен быть хешируемым: иметь стабильный __hash__, согласованный с __eq__ — равные объекты обязаны иметь равный хеш// разбор: set и dict кладут объект в корзину по его __hash__ и различают объекты в корзине через __eq__. Требования: хеш стабилен всё время, пока объект в контейнере (поэтому изменяемые объекты не годятся), и согласован с равенством — если a==b, то hash(a)==hash(b). Свои классы класть можно, если корректно определить оба метода.
- sorted(data, key=...) в Python — устойчивая (stable) сортировка. Что это даёт на практике?A)Устойчивость означает, что итоговый результат сортировки вообще никак не зависит от исходного порядка элементов в переданной коллекцииB)Элементы с равными ключами сохраняют исходный взаимный порядок — можно сортировать в несколько проходов по разным ключамC)Устойчивость обеспечивает, что сортировка данных выполняется за линейное время O(n), а не O(n log n)D)Это значит, что sorted не падает с ошибкой, даже если отдельные элементы коллекции несравнимы по типу
показать ответ и разбор
+B)Элементы с равными ключами сохраняют исходный взаимный порядок — можно сортировать в несколько проходов по разным ключам// разбор: Стабильная сортировка (в CPython — Timsort) сохраняет исходный взаимный порядок элементов с РАВНЫМИ ключами. Практическая польза: многоуровневую сортировку делают последовательными проходами от менее к более значимому ключу (сначала по имени, потом по дате — совпадающие даты останутся в порядке по имени) или задают составной ключ key=lambda x:(x.a, x.b). Устойчивость — про порядок равных, а не про сложность (O(n log n)) или независимость от входа.
- Чем tuple отличается от list в Python?A)tuple быстрее, потому что хранится на стеке, а не в кучеB)tuple может хранить элементы разных типов, list — нетC)tuple неизменяем — элементы не заменитьD)разницы нет — tuple просто другой синтаксис списка
показать ответ и разбор
+C)tuple неизменяем — элементы не заменить// разбор: tuple после создания не меняется: нельзя заменить, добавить или удалить элемент — t[0] = 1 даёт TypeError. Следствия практичны: tuple хешируем (годится ключом dict и элементом set), защищает данные от случайной правки и компактнее в памяти. Изменяемая последовательность — list, «замороженная» — tuple.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.