|
| 1 | +# Коллекции в Python: от основ к тонкостям |
| 2 | + |
| 3 | +Коллекции — это основа хранения и обработки данных в Python. Понимание их внутреннего устройства, сильных и слабых сторон — ключ к написанию эффективного и идиоматичного кода. |
| 4 | + |
| 5 | +## Что такое коллекция? |
| 6 | + |
| 7 | +В Python **коллекция** — это объект, который одновременно является: |
| 8 | +- **Контейнером** (`Container`) — поддерживает оператор `in` для проверки вхождения. |
| 9 | +- **Итерируемым объектом** (`Iterable`) — его элементы можно перебирать в цикле. |
| 10 | +- **Объектом ограниченной длины** (`Sized`) — поддерживает функцию `len()`. |
| 11 | + |
| 12 | +```python |
| 13 | +from collections.abc import Collection |
| 14 | + |
| 15 | +# Проверим, является ли список коллекцией |
| 16 | +print(isinstance([1, 2, 3], Collection)) # True |
| 17 | +``` |
| 18 | + |
| 19 | +### Любопытные исключения |
| 20 | + |
| 21 | +- **Интервал чисел** может быть контейнером, но не быть итерируемым или иметь длину. |
| 22 | +- **Генератор** является итерируемым, но не контейнером и не имеет длины. |
| 23 | + |
| 24 | +```python |
| 25 | +# Пример: Интервал как контейнер |
| 26 | +from dataclasses import dataclass |
| 27 | + |
| 28 | +@dataclass |
| 29 | +class Interval: |
| 30 | + a: float |
| 31 | + b: float |
| 32 | + |
| 33 | + def __contains__(self, x): |
| 34 | + return self.a < x < self.b |
| 35 | + |
| 36 | +interval = Interval(0, 1) |
| 37 | +print(0.5 in interval) # True |
| 38 | +print(len(interval)) # TypeError: object of type 'Interval' has no len() |
| 39 | +``` |
| 40 | + |
| 41 | +## Иерархия коллекций |
| 42 | + |
| 43 | +Стандартная библиотека Python определяет абстрактные базовые классы (ABC) для классификации коллекций: |
| 44 | + |
| 45 | +``` |
| 46 | +Container -> Iterable -> Sized -> Collection |
| 47 | + | |
| 48 | + --------+-------- |
| 49 | + | | |
| 50 | + Sequence Mapping |
| 51 | +``` |
| 52 | + |
| 53 | +- **Sequence** (Последовательности): элементы упорядочены и доступны по индексу (списки, кортежи, строки). |
| 54 | +- **Mapping** (Отображения): хранят пары «ключ-значение» (словари). |
| 55 | + |
| 56 | +## Списки (list): универсальные и изменяемые |
| 57 | + |
| 58 | +Списки — это, пожалуй, самая часто используемая коллекция. |
| 59 | + |
| 60 | +### Неочевидные особенности инициализации |
| 61 | + |
| 62 | +```python |
| 63 | +# Кажется, что это создаст матрицу 2x1 |
| 64 | +chunks = [[0]] * 2 |
| 65 | +chunks[0][0] = 42 |
| 66 | +print(chunks) # [[42], [42]] Оба элемента ссылаются на один и тот же список! |
| 67 | + |
| 68 | +# Правильные способы инициализации матрицы: |
| 69 | +correct_chunks = [[0] for _ in range(2)] |
| 70 | +correct_chunks = [[0]] * 2 # Так делать нельзя, если планируется изменение вложенных списков! |
| 71 | +``` |
| 72 | + |
| 73 | +### Эффективные операции |
| 74 | + |
| 75 | +- `append(item)` и `pop()` — амортизированная сложность O(1). |
| 76 | +- `insert(0, item)` и `pop(0)` — сложность O(n), так как требуют сдвига всех элементов. |
| 77 | +- `extend(iterable)` эффективнее, чем многократный вызов `append()`. |
| 78 | + |
| 79 | +**Совет:** Используйте `collections.deque`, если вам нужны частые операции с обоих концов. |
| 80 | + |
| 81 | +## Кортежи (tuple): неизменяемые и хэшируемые |
| 82 | + |
| 83 | +Кортежи не только защищают данные от изменений, но и могут быть использованы как ключи в словарях или элементы множеств, так как они **хэшируемы**. |
| 84 | + |
| 85 | +### Распаковка кортежей — мощный инструмент |
| 86 | + |
| 87 | +```python |
| 88 | +# Распаковка с упаковкой «лишних» элементов в переменную |
| 89 | +first, second, *rest = range(10) |
| 90 | +print(first, second, rest) # 0 1 [2, 3, 4, 5, 6, 7, 8, 9] |
| 91 | + |
| 92 | +# Игнорирование ненужных значений |
| 93 | +x, _, z = (1, 2, 3) |
| 94 | + |
| 95 | +# Распаковка в аргументы функции |
| 96 | +def greet(name, greeting): |
| 97 | + return f"{greeting}, {name}!" |
| 98 | + |
| 99 | +person = ("Alice", "Hello") |
| 100 | +print(greet(*person)) # "Hello, Alice!" |
| 101 | +``` |
| 102 | + |
| 103 | +### Именованные кортежи (namedtuple) |
| 104 | + |
| 105 | +`collections.namedtuple` — это фабрика классов, создающая подтип кортежа с именованными полями. Это делает код самодокументируемым. |
| 106 | + |
| 107 | +```python |
| 108 | +from collections import namedtuple |
| 109 | + |
| 110 | +Point = namedtuple('Point', ['x', 'y']) |
| 111 | +p = Point(10, y=20) |
| 112 | +print(p.x, p.y) # 10 20 |
| 113 | +print(p._asdict()) # {'x': 10, 'y': 20} |
| 114 | +``` |
| 115 | + |
| 116 | +## Множества (set): уникальность и скорость |
| 117 | + |
| 118 | +Множества реализованы как хэш-таблицы, поэтому проверка вхождения (`in`) имеет среднюю сложность O(1). |
| 119 | + |
| 120 | +### Неочевидные применения множеств |
| 121 | + |
| 122 | +1. **Удаление дубликатов из списка** — классика, но всегда актуально. |
| 123 | + ```python |
| 124 | + unique_list = list(set(duplicated_list)) |
| 125 | + ``` |
| 126 | + |
| 127 | +2. **Подсчет общих элементов** между двумя коллекциями. |
| 128 | + ```python |
| 129 | + common = set(list1) & set(list2) |
| 130 | + ``` |
| 131 | + |
| 132 | +3. **Фильтрация «мусора»** при разборе данных. |
| 133 | + ```python |
| 134 | + valid_tags = {'python', 'tutorial', 'advanced'} |
| 135 | + tags = ['python', 'beginner', 'advanced'] |
| 136 | + filtered_tags = [tag for tag in tags if tag in valid_tags] |
| 137 | + # ['python', 'advanced'] |
| 138 | + ``` |
| 139 | + |
| 140 | +### frozenset: неизменяемое множество |
| 141 | + |
| 142 | +Поскольку обычные множества изменяемы и, следовательно, нехэшируемы, их нельзя вложить в другие множества или использовать как ключи словаря. Для этого нужен `frozenset`. |
| 143 | + |
| 144 | +```python |
| 145 | +# Множество множеств? Нет. |
| 146 | +# {set([1,2]), set([3,4])} # TypeError: unhashable type: 'set' |
| 147 | + |
| 148 | +# А так — можно. |
| 149 | +fs1 = frozenset([1, 2]) |
| 150 | +fs2 = frozenset([3, 4]) |
| 151 | +meta_set = {fs1, fs2} # Valid |
| 152 | +``` |
| 153 | + |
| 154 | +## Словари (dict): сердце Python |
| 155 | + |
| 156 | +Современные словари (Python 3.7+) сохраняют **порядок добавления** элементов. |
| 157 | + |
| 158 | +### Малоизвестные возможности словарей |
| 159 | + |
| 160 | +1. **Метод `setdefault()`** — проверяет наличие ключа и, если его нет, устанавливает значение за один проход по хэш-таблице. |
| 161 | + |
| 162 | + ```python |
| 163 | + data = {} |
| 164 | + # Классический, но неэффективный способ |
| 165 | + if 'key' not in data: |
| 166 | + data['key'] = [] |
| 167 | + data['key'].append(1) |
| 168 | + |
| 169 | + # Эффективный способ с setdefault |
| 170 | + data.setdefault('key', []).append(1) |
| 171 | + ``` |
| 172 | + |
| 173 | +2. **Метод `popitem()`** — удаляет и возвращает пару `(ключ, значение)` в порядке LIFO (последним пришел — первым ушел). Полезно для обработки данных в обратном порядке. |
| 174 | + |
| 175 | +3. **Словарные включения (Dict Comprehensions)** — компактный и выразительный синтаксис. |
| 176 | + |
| 177 | + ```python |
| 178 | + squares = {x: x*x for x in range(5)} |
| 179 | + # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} |
| 180 | + ``` |
| 181 | + |
| 182 | +### Коллекции из модуля `collections` |
| 183 | + |
| 184 | +Модуль `collections` предоставляет специализированные типы данных, которые расширяют возможности стандартных коллекций. |
| 185 | + |
| 186 | +- **`defaultdict`** — словарь с заводской функцией для отсутствующих ключей. |
| 187 | + ```python |
| 188 | + from collections import defaultdict |
| 189 | + graph = defaultdict(list) |
| 190 | + graph['a'].append('b') # Не нужно проверять, есть ли ключ 'a' |
| 191 | + ``` |
| 192 | + |
| 193 | +- **`Counter`** — подкласс словаря для подсчета хэшируемых объектов. |
| 194 | + ```python |
| 195 | + from collections import Counter |
| 196 | + words = ['apple', 'banana', 'apple', 'orange'] |
| 197 | + word_count = Counter(words) |
| 198 | + print(word_count.most_common(1)) # [('apple', 2)] |
| 199 | + ``` |
| 200 | + |
| 201 | +- **`deque`** — двусторонняя очередь. Идеальна для очередей (FIFO) и стеков (LIFO). |
| 202 | + ```python |
| 203 | + from collections import deque |
| 204 | + queue = deque([1, 2, 3]) |
| 205 | + queue.append(4) # O(1) |
| 206 | + queue.popleft() # O(1) - в отличие от списка! |
| 207 | + ``` |
| 208 | + |
| 209 | +- **`OrderedDict`** — словарь, который помнит порядок. В Python 3.7+ обычный `dict` тоже упорядочен, но `OrderedDict` имеет дополнительные методы (`move_to_end`, `popitem(last=True/False)`). |
| 210 | + |
| 211 | +## Заключение |
| 212 | + |
| 213 | +Выбор правильной коллекции — это не просто вопрос синтаксиса. Это вопрос эффективности, читаемости и корректности вашего кода. |
| 214 | + |
| 215 | +- Используйте **списки** для упорядоченных коллекций, которые могут изменяться. |
| 216 | +- Используйте **кортежи** для фиксированных данных или когда нужны хэшируемые объекты. |
| 217 | +- Используйте **множества** для проверки уникальности и быстрого поиска. |
| 218 | +- Используйте **словари** для отображений ключей на значения. |
| 219 | +- Не забывайте о **специализированных коллекциях** из модуля `collections` для решения специфических задач. |
| 220 | + |
| 221 | +Глубокое понимание коллекций позволит вам писать код, который не только работает, но и работает **хорошо**. |
0 commit comments