Коллекции в Python: от основ к тонкостям
Коллекции — это основа хранения и обработки данных в Python. Понимание их внутреннего устройства, сильных и слабых сторон — ключ к написанию эффективного и идиоматичного кода.
Что такое коллекция?
В Python коллекция — это объект, который одновременно является:
- Контейнером (
Container) — поддерживает операторinдля проверки вхождения. - Итерируемым объектом (
Iterable) — его элементы можно перебирать в цикле. - Объектом ограниченной длины (
Sized) — поддерживает функциюlen().
from collections.abc import Collection
# Проверим, является ли список коллекцией
print(isinstance([1, 2, 3], Collection)) # True
Любопытные исключения
- Интервал чисел может быть контейнером, но не быть итерируемым или иметь длину.
- Генератор является итерируемым, но не контейнером и не имеет длины.
# Пример: Интервал как контейнер
from dataclasses import dataclass
@dataclass
class Interval:
a: float
b: float
def __contains__(self, x):
return self.a < x < self.b
interval = Interval(0, 1)
print(0.5 in interval) # True
print(len(interval)) # TypeError: object of type 'Interval' has no len()
Иерархия коллекций
Стандартная библиотека Python определяет абстрактные базовые классы (ABC) для классификации коллекций:
Container -> Iterable -> Sized -> Collection
|
--------+--------
| |
Sequence Mapping
- Sequence (Последовательности): элементы упорядочены и доступны по индексу (списки, кортежи, строки).
- Mapping (Отображения): хранят пары «ключ-значение» (словари).
Списки (list): универсальные и изменяемые
Списки — это, пожалуй, самая часто используемая коллекция.
Неочевидные особенности инициализации
# Кажется, что это создаст матрицу 2x1
chunks = [[0]] * 2
chunks[0][0] = 42
print(chunks) # [[42], [42]] Оба элемента ссылаются на один и тот же список!
# Правильные способы инициализации матрицы:
correct_chunks = [[0] for _ in range(2)]
correct_chunks = [[0]] * 2 # Так делать нельзя, если планируется изменение вложенных списков!
Эффективные операции
append(item)иpop()— амортизированная сложность O(1).insert(0, item)иpop(0)— сложность O(n), так как требуют сдвига всех элементов.extend(iterable)эффективнее, чем многократный вызовappend().
Совет: Используйте collections.deque, если вам нужны частые операции с обоих концов.
Кортежи (tuple): неизменяемые и хэшируемые
Кортежи не только защищают данные от изменений, но и могут быть использованы как ключи в словарях или элементы множеств, так как они хэшируемы.
Распаковка кортежей — мощный инструмент
# Распаковка с упаковкой «лишних» элементов в переменную
first, second, *rest = range(10)
print(first, second, rest) # 0 1 [2, 3, 4, 5, 6, 7, 8, 9]
# Игнорирование ненужных значений
x, _, z = (1, 2, 3)
# Распаковка в аргументы функции
def greet(name, greeting):
return f"{greeting}, {name}!"
person = ("Alice", "Hello")
print(greet(*person)) # "Hello, Alice!"
Именованные кортежи (namedtuple)
collections.namedtuple — это фабрика классов, создающая подтип кортежа с именованными полями. Это делает код самодокументируемым.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, y=20)
print(p.x, p.y) # 10 20
print(p._asdict()) # {'x': 10, 'y': 20}
Множества (set): уникальность и скорость
Множества реализованы как хэш-таблицы, поэтому проверка вхождения (in) имеет среднюю сложность O(1).
Неочевидные применения множеств
-
Удаление дубликатов из списка — классика, но всегда актуально.
unique_list = list(set(duplicated_list)) -
Подсчет общих элементов между двумя коллекциями.
common = set(list1) & set(list2) -
Фильтрация «мусора» при разборе данных.
valid_tags = {'python', 'tutorial', 'advanced'} tags = ['python', 'beginner', 'advanced'] filtered_tags = [tag for tag in tags if tag in valid_tags] # ['python', 'advanced']
frozenset: неизменяемое множество
Поскольку обычные множества изменяемы и, следовательно, нехэшируемы, их нельзя вложить в другие множества или использовать как ключи словаря. Для этого нужен frozenset.
# Множество множеств? Нет.
# {set([1,2]), set([3,4])} # TypeError: unhashable type: 'set'
# А так — можно.
fs1 = frozenset([1, 2])
fs2 = frozenset([3, 4])
meta_set = {fs1, fs2} # Valid
Словари (dict): сердце Python
Современные словари (Python 3.7+) сохраняют порядок добавления элементов.
Малоизвестные возможности словарей
-
Метод
setdefault()— проверяет наличие ключа и, если его нет, устанавливает значение за один проход по хэш-таблице.data = {} # Классический, но неэффективный способ if 'key' not in data: data['key'] = [] data['key'].append(1) # Эффективный способ с setdefault data.setdefault('key', []).append(1) -
Метод
popitem()— удаляет и возвращает пару(ключ, значение)в порядке LIFO (последним пришел — первым ушел). Полезно для обработки данных в обратном порядке. -
Словарные включения (Dict Comprehensions) — компактный и выразительный синтаксис.
squares = {x: x*x for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Коллекции из модуля collections
Модуль collections предоставляет специализированные типы данных, которые расширяют возможности стандартных коллекций.
-
defaultdict— словарь с заводской функцией для отсутствующих ключей.from collections import defaultdict graph = defaultdict(list) graph['a'].append('b') # Не нужно проверять, есть ли ключ 'a' -
Counter— подкласс словаря для подсчета хэшируемых объектов.from collections import Counter words = ['apple', 'banana', 'apple', 'orange'] word_count = Counter(words) print(word_count.most_common(1)) # [('apple', 2)] -
deque— двусторонняя очередь. Идеальна для очередей (FIFO) и стеков (LIFO).from collections import deque queue = deque([1, 2, 3]) queue.append(4) # O(1) queue.popleft() # O(1) - в отличие от списка! -
OrderedDict— словарь, который помнит порядок. В Python 3.7+ обычныйdictтоже упорядочен, ноOrderedDictимеет дополнительные методы (move_to_end,popitem(last=True/False)).
Заключение
Выбор правильной коллекции — это не просто вопрос синтаксиса. Это вопрос эффективности, читаемости и корректности вашего кода.
- Используйте списки для упорядоченных коллекций, которые могут изменяться.
- Используйте кортежи для фиксированных данных или когда нужны хэшируемые объекты.
- Используйте множества для проверки уникальности и быстрого поиска.
- Используйте словари для отображений ключей на значения.
- Не забывайте о специализированных коллекциях из модуля
collectionsдля решения специфических задач.
Глубокое понимание коллекций позволит вам писать код, который не только работает, но и работает хорошо.