No history yet

Продвинутые структуры данных

Коллекции для профессионалов

Стандартные списки и словари в Python — мощные инструменты, но по мере роста сложности задач их возможностей может не хватать. Когда производительность и читаемость кода выходят на первый план, на помощь приходит модуль collections. Он предлагает специализированные структуры данных, оптимизированные для конкретных сценариев.

Именованные кортежи: данные с именами

Представьте, что вы работаете с данными, где каждый элемент — это кортеж, например, ('Alice', 30, 'Engineer'). Чтобы получить возраст, вам придется использовать индекс person[1]. Это неинформативно и легко приводит к ошибкам. Использование словаря решило бы проблему читаемости, но словари изменяемы и занимают больше памяти.

namedtuple предлагает лучшее из двух миров: он создает неизменяемые, легковесные объекты, похожие на кортежи, но с именованными полями. Это делает код самодокументируемым.

from collections import namedtuple

# Определяем структуру 'Person'
Person = namedtuple('Person', ['name', 'age', 'job'])

# Создаем экземпляр
alice = Person(name='Alice', age=30, job='Engineer')

# Доступ к данным по имени, а не по индексу
print(f"{alice.name} is {alice.age} years old.")
# Вывод: Alice is 30 years old.

Используйте namedtuple для создания простых неизменяемых структур данных, когда полноценный класс кажется избыточным. Это улучшает читаемость кода без ущерба для производительности.

Эффективные очереди с deque

Обычный список Python (list) плохо подходит для реализации очереди. При удалении первого элемента (list.pop(0)) всем остальным элементам приходится сдвигаться на одну позицию влево. Эта операция имеет временную сложность O(n)O(n), где nn — количество элементов. В больших списках это очень медленно.

deque (произносится как «дек», от double-ended queue) — это двусторонняя очередь, созданная для быстрых добавлений и удалений с обоих концов. Все эти операции выполняются за константное время, O(1)O(1).

from collections import deque

# Создаем очередь
queue = deque(['task1', 'task2', 'task3'])

# Добавляем элемент в конец (как в списке)
queue.append('task4')

# Удаляем первый элемент — это очень быстро!
first_task = queue.popleft()

print(f"Processing: {first_task}")
print(f"Remaining tasks: {queue}")
# Вывод:
# Processing: task1
# Remaining tasks: deque(['task2', 'task3', 'task4'])

deque идеально подходит для реализации очередей, стеков или для хранения последних N элементов в истории.

Подсчет и умолчания

Две другие полезные структуры в collections — это Counter и defaultdict.

Counter — это подкласс словаря для подсчета хэшируемых объектов. Он избавляет от необходимости вручную инициализировать счетчики в цикле.

from collections import Counter

words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']

word_counts = Counter(words)

print(word_counts)
# Вывод: Counter({'apple': 3, 'banana': 2, 'orange': 1})

# Найти два самых частых слова
print(word_counts.most_common(2))
# Вывод: [('apple', 3), ('banana', 2)]

defaultdict ведет себя как обычный словарь, за исключением того, что он никогда не вызовет ошибку KeyError. Если ключ отсутствует, он автоматически создает для него значение по умолчанию с помощью предоставленной функции-фабрики (например, int, list или set). Это сильно упрощает код, особенно при группировке данных.

from collections import defaultdict

# Создаем словарь, который по умолчанию создает пустой список для новых ключей
data_by_group = defaultdict(list)

data = [('A', 1), ('B', 2), ('A', 3), ('C', 4), ('B', 5)]

for group, value in data:
    # Не нужно проверять, существует ли ключ. Просто добавляем.
    data_by_group[group].append(value)

print(data_by_group)
# Вывод: defaultdict(<class 'list'>, {'A': [1, 3], 'B': [2, 5], 'C': [4]})

Копирование: поверхностное и глубокое

При работе со сложными структурами данных, содержащими вложенные объекты (например, списки списков), важно понимать разницу между поверхностным и глубоким копированием.

Поверхностное копирование (с помощью copy.copy() или методов вроде list.copy()) создает новый объект, но заполняет его ссылками на те же самые вложенные объекты, что и в оригинале. Изменение вложенного объекта в копии затронет и оригинал.

import copy

original_list = [[1, 2], [3, 4]]
shallow_copy = copy.copy(original_list)

# Изменяем вложенный список в копии
shallow_copy[0][0] = 'X'

print(f"Shallow copy: {shallow_copy}")
print(f"Original list: {original_list}") # Оригинал тоже изменился!
# Вывод:
# Shallow copy: [['X', 2], [3, 4]]
# Original list: [['X', 2], [3, 4]]

Глубокое копирование (с помощью copy.deepcopy()) создает полную, независимую копию. Оно рекурсивно копирует все объекты, включая вложенные. Изменения в копии никак не влияют на оригинал.

import copy

original_list = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original_list)

# Изменяем вложенный список в копии
deep_copy[0][0] = 'X'

print(f"Deep copy: {deep_copy}")
print(f"Original list: {original_list}") # Оригинал остался нетронутым
# Вывод:
# Deep copy: [['X', 2], [3, 4]]
# Original list: [[1, 2], [3, 4]]

Используйте copy.copy() для простых структур или когда вы намеренно хотите сохранить связь между вложенными объектами. Прибегайте к copy.deepcopy(), когда вам нужна полная независимость копии от оригинала.

Quiz Questions 1/6

В чем основное преимущество использования namedtuple из модуля collections по сравнению с обычным кортежем (tuple)?

Quiz Questions 2/6

Какая операция будет значительно быстрее выполняться на deque по сравнению со стандартным списком (list) Python, особенно при большом количестве элементов?

Выбор правильной структуры данных — ключевой шаг к написанию эффективного и чистого кода. Инструменты из модуля collections помогают решать стандартные задачи более элегантно и производительно.