В Python коллекция — это объект, содержащий группу элементов и позволяющий с ними работать как с единым целым.
Коллекции обычно поддерживают:
- итерацию (
for item in collection) - проверку вхождения (
x in collection) - определение длины (
len(collection)) - доступ по индексу или ключу (если упорядочены или ассоциативны)
💡 В Python нет строгого интерфейса «коллекция», но есть неформальные протоколы. Если объект поддерживает
__iter__,__len__,__contains__— его можно считать коллекцией.
- Что не является коллекцией
- Основные встроенные коллекции
- Расширенные коллекции из стандартной библиотеки
- Другие коллекции и коллекционоподобные типы
- 1. Списки —
list - 2. Словари —
dict - 3. Кортежи —
tuple - 4. SimpleNamespace
- 5. Множества —
set - 6. Неизменяемые множества —
frozenset - 7. namedtuple — именованные кортежи
- 8. deque — двусторонняя очередь
- 9. Counter — счётчик элементов
- 10. defaultdict — словарь со значениями по умолчанию
- 11. dataclass — классы данных
- 12. UserList — кастомные списки
- 13. UserDict — кастомные словари
- 📈 Сравнение коллекций по памяти и скорости
- 1. Память:
listvstuplevsarray.array - 2. Скорость доступа:
listvstuplevsarray.array - 3. Память:
dictvsSimpleNamespacevsdataclass - 4. Экономия памяти:
dataclassс__slots__ - 5. Скорость поиска:
listvsset - 6. Память:
setvsfrozenset - 7. Скорость добавления:
list.appendvsdeque.appendvsdeque.appendleft
- 1. Память:
- 🧠 Общие рекомендации по производительности
- 📊 Сравнение коллекций
- 💡 Когда что использовать?
Что не является коллекцией
Следующие типы не считаются коллекциями, так как не содержат группы элементов:
int,float,bool— скалярные значенияNone— отсутствие значения- функции, модули, классы — это объекты, но не контейнеры данных (если только не содержат
__dict__)
Основные встроенные коллекции
Доступны без импортов:
| Тип | Описание |
|---|---|
list | Упорядоченный, изменяемый список. |
tuple | Упорядоченный, неизменяемый кортеж. |
dict | Упорядоченный (с Python 3.7+) словарь ключ-значение. |
set | Неупорядоченное множество уникальных элементов. |
frozenset | Неизменяемая версия set. |
Расширенные коллекции из стандартной библиотеки
| Тип | Модуль | Зачем нужен |
|---|---|---|
SimpleNamespace | types | Объект с динамическими атрибутами (альтернатива словарю с доступом через точку). |
namedtuple | collections | Неизменяемый кортеж с именованными полями. |
deque | collections | Двусторонняя очередь — эффективна для операций на концах. |
Counter | collections | Словарь-счётчик частот элементов. |
defaultdict | collections | Словарь со значениями по умолчанию. |
dataclass | dataclasses | Автоматически генерирует классы данных с __init__, __repr__, __eq__ и др. |
UserList | collections | Базовый класс для создания кастомных списков. |
UserDict | collections | Базовый класс для создания кастомных словарей. |
Другие коллекции и коллекционоподобные типы
Хотя не всегда называются «коллекциями» в бытовом смысле, эти типы тоже хранят или представляют группы данных.
1. str — строка
Неизменяемая упорядоченная коллекция символов.
s = "Python"
print(len(s)) # → 6
print(s[0]) # → P
print('y' in s) # → True
print(list(s)) # → ['P', 'y', 't', 'h', 'o', 'n']
2. bytes, bytearray
bytes— неизменяемая последовательность байтов.bytearray— изменяемая версия.
b = b"hello"
print(b[0]) # → 104
print(len(b)) # → 5
ba = bytearray(b"hello")
ba[0] = 72
print(ba) # → bytearray(b'Hello')
3. range
Ленивая упорядоченная последовательность чисел. Не хранит элементы в памяти.
r = range(3)
print(list(r)) # → [0, 1, 2]
print(1 in r) # → True
print(r[2]) # → 2
4. array.array
Хранит однотипные числовые данные компактно (как в C).
from array import array
arr = array('i', [1, 2, 3]) # 'i' = signed int
print(arr) # → array('i', [1, 2, 3])
5. Генераторы и итераторы
Не хранят данные — генерируют по запросу. Не поддерживают len() или индексацию.
gen = (x * 2 for x in range(3))
print(list(gen)) # → [0, 2, 4]
# len(gen) → TypeError
6. ChainMap (из collections)
Объединяет несколько словарей в одну виртуальную коллекцию — поиск идёт по цепочке.
from collections import ChainMap
d1 = {'a': 1}
d2 = {'b': 2}
cm = ChainMap(d1, d2)
print(cm['a']) # → 1
print(cm['b']) # → 2
7. OrderedDict (из collections)
Словарь с гарантированным порядком вставки. Актуален для Python < 3.7.
from collections import OrderedDict
od = OrderedDict([('a', 1), ('b', 2)])
print(od) # → OrderedDict([('a', 1), ('b', 2)])
8. enum.Enum, enum.Flag
Коллекции именованных констант
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
print(list(Color)) # → [, ]
9. typing.NamedTuple, typing.TypedDict
Типизированные обёртки над namedtuple и dict.
from typing import NamedTuple, TypedDict
class Person(NamedTuple):
name: str
age: int
p = Person("Алиса", 25)
class Movie(TypedDict):
title: str
year: int
m: Movie = {"title": "Matrix", "year": 1999}
10. heapq, bisect — инструменты, а не коллекции
Работают с коллекциями, но сами коллекциями не являются:
heapq— поддержка кучи через списки.bisect— вставка в отсортированный список с сохранением порядка.
1. Списки — list
Упорядоченная, изменяемая коллекция. Элементы могут повторяться, типы — любые.
Применяется, когда нужна гибкая последовательность: добавление, удаление, изменение элементов.
Создание: []
boris_list = ["Борис", "Москва", 30, "инженер"]
print(f"Создание списка: {boris_list}")
# → Создание списка: ['Борис', 'Москва', 30, 'инженер']
print(f"Элемент по индексу 0: {boris_list[0]}")
# → Элемент по индексу 0: Борис
boris_list[2] = 31
print(f"Изменение элемента: {boris_list}")
# → Изменение элемента: ['Борис', 'Москва', 31, 'инженер']
boris_list.append("женат")
print(f"Добавление в конец: {boris_list}")
# → Добавление в конец: ['Борис', 'Москва', 31, 'инженер', 'женат']
boris_list.insert(1, "Россия")
print(f"Вставка элемента: {boris_list}")
# → Вставка элемента: ['Борис', 'Россия', 'Москва', 31, 'инженер', 'женат']
boris_list.remove("инженер")
print(f"Удаление по значению: {boris_list}")
# → Удаление по значению: ['Борис', 'Россия', 'Москва', 31, 'женат']
del boris_list[2]
print(f"Удаление по индексу: {boris_list}")
# → Удаление по индексу: ['Борис', 'Россия', 31, 'женат']
boris_list.extend(["хобби", "рыбалка"])
print(f"Расширение списка: {boris_list}")
# → Расширение списка: ['Борис', 'Россия', 31, 'женат', 'хобби', 'рыбалка']
boris_list.pop()
print(f"Удаление с конца: {boris_list}")
# → Удаление с конца: ['Борис', 'Россия', 31, 'женат', 'хобби']
2. Словари — dict
Коллекция пар ключ → значение. Ключи должны быть хешируемыми. С Python 3.7 сохраняет порядок вставки.
Полезен для структурированных данных: профили, конфиги, JSON.
Создание: {}
alice_dict = {"name": "Алиса", "age": 25, "city": "Лондон", "occupation": "художница"}
print(f"Создание словаря: {alice_dict}")
# → Создание словаря: {'name': 'Алиса', 'age': 25, 'city': 'Лондон', 'occupation': 'художница'}
print(f"Значение по ключу 'name': {alice_dict['name']}")
# → Значение по ключу 'name': Алиса
alice_dict["age"] = 26
print(f"Изменение значения: {alice_dict}")
# → Изменение значения: {'name': 'Алиса', 'age': 26, 'city': 'Лондон', 'occupation': 'художница'}
alice_dict["hobby"] = "рисование"
print(f"Добавление пары: {alice_dict}")
# → Добавление пары: {'name': 'Алиса', 'age': 26, 'city': 'Лондон', 'occupation': 'художница', 'hobby': 'рисование'}
del alice_dict["city"]
print(f"Удаление пары: {alice_dict}")
# → Удаление пары: {'name': 'Алиса', 'age': 26, 'occupation': 'художница', 'hobby': 'рисование'}
hobby = alice_dict.pop("hobby")
print(f"Удаление с возвратом: {alice_dict}, значение: {hobby}")
# → Удаление с возвратом: {'name': 'Алиса', 'age': 26, 'occupation': 'художница'}, значение: рисование
print(f"Есть ли ключ 'name': {'name' in alice_dict}")
# → Есть ли ключ 'name': True
3. Кортежи — tuple
Упорядоченная, неизменяемая коллекция. Подходит для фиксированных данных.
Используется, когда важна неизменяемость: координаты, параметры, возвращаемые значения.
Создание: ()
boris_tuple = ("Борис", "Москва", 30, "инженер")
print(f"Создание кортежа: {boris_tuple}")
# → Создание кортежа: ('Борис', 'Москва', 30, 'инженер')
print(f"Элемент по индексу 2: {boris_tuple[2]}")
# → Элемент по индексу 2: 30
# boris_tuple[0] = "Иван" → TypeError
# boris_tuple.append("что-то") → AttributeError
💡 Кортежи занимают меньше памяти и работают быстрее списков. Идеальны, когда изменяемость не нужна.
4. SimpleNamespace
Простой класс из модуля types для создания объектов с динамическими атрибутами. Доступ — через точку (obj.attr).
Полезен, когда нужен синтаксис obj.name без создания класса.
from types import SimpleNamespace
alice_ns = SimpleNamespace(name="Алиса", age=25, city="Лондон")
print(f"Объект: {alice_ns}")
# → Объект: namespace(name='Алиса', age=25, city='Лондон')
print(f"Имя: {alice_ns.name}")
# → Имя: Алиса
alice_ns.age = 26
print(f"После изменения: {alice_ns}")
# → После изменения: namespace(name='Алиса', age=26, city='Лондон')
alice_ns.occupation = "художница"
print(f"С новым атрибутом: {alice_ns}")
# → С новым атрибутом: namespace(name='Алиса', age=26, city='Лондон', occupation='художница')
del alice_ns.city
print(f"После удаления: {alice_ns}")
# → После удаления: namespace(name='Алиса', age=26, occupation='художница')
setattr(alice_ns, "hobby", "рисование")
print(f"Через setattr: {alice_ns}")
# → Через setattr: namespace(name='Алиса', age=26, occupation='художница', hobby='рисование')
delattr(alice_ns, "hobby")
print(f"Через delattr: {alice_ns}")
# → Через delattr: namespace(name='Алиса', age=26, occupation='художница')
💡 Альтернатива словарю, когда удобнее писать
obj.name, а неobj['name'].
5. Множества — set
Неупорядоченная коллекция уникальных элементов. Поддерживает операции: объединение, пересечение, разность.
Применяется для удаления дубликатов и проверки вхождения.
Создание: {} или set()
numbers = {1, 2, 3, 3, 2, 1}
print(f"Множество: {numbers}")
# → Множество: {1, 2, 3}
numbers.add(4)
print(f"После добавления: {numbers}")
# → После добавления: {1, 2, 3, 4}
numbers.remove(2)
print(f"После удаления: {numbers}")
# → После удаления: {1, 3, 4}
other = {3, 4, 5}
print(f"Объединение: {numbers | other}")
# → Объединение: {1, 3, 4, 5}
print(f"Пересечение: {numbers & other}")
# → Пересечение: {3, 4}
print(f"Разность: {numbers - other}")
# → Разность: {1}
6. Неизменяемые множества — frozenset
Неизменяемая версия set. Можно использовать как ключ в словаре или элемент другого множества.
frozen = frozenset([1, 2, 3, 2])
print(f"frozenset: {frozen}")
# → frozenset: frozenset({1, 2, 3})
other = frozenset([3, 4])
print(f"Пересечение: {frozen & other}")
# → Пересечение: frozenset({3})
print(f"Объединение: {frozen | other}")
# → Объединение: frozenset({1, 2, 3, 4})
# frozen.add(5) → AttributeError
7. namedtuple — именованные кортежи
Неизменяемая структура с доступом по именам полей. Аналог кортежа, но читаемее.
from collections import namedtuple
Person = namedtuple("Person", ["name", "age", "city"])
alice = Person("Алиса", 25, "Лондон")
print(f"Объект: {alice}")
# → Объект: Person(name='Алиса', age=25, city='Лондон')
print(f"Имя: {alice.name}")
# → Имя: Алиса
print(f"Возраст: {alice[1]}")
# → Возраст: 25
# alice.age = 26 → AttributeError (неизменяемый)
alice_new = alice._replace(age=26)
print(f"Копия с изменением: {alice_new}")
# → Копия с изменением: Person(name='Алиса', age=26, city='Лондон')
💡 Отлично подходит для представления записей: точки, пользователи, конфиги — когда важна неизменяемость и читаемость.
8. deque — двусторонняя очередь
Оптимизирован для быстрых операций в начале и конце. Аналог списка, но эффективнее для appendleft, popleft.
from collections import deque
d = deque([1, 2, 3])
print(f"Исходный deque: {d}")
# → Исходный deque: deque([1, 2, 3])
d.appendleft(0)
print(f"Добавление слева: {d}")
# → Добавление слева: deque([0, 1, 2, 3])
d.append(4)
print(f"Добавление справа: {d}")
# → Добавление справа: deque([0, 1, 2, 3, 4])
left = d.popleft()
print(f"Удаление слева: {left}, осталось: {d}")
# → Удаление слева: 0, осталось: deque([1, 2, 3, 4])
right = d.pop()
print(f"Удаление справа: {right}, осталось: {d}")
# → Удаление справа: 4, осталось: deque([1, 2, 3])
💡 Используется в алгоритмах: BFS, кэширование (LRU), буферы, где важна производительность на концах.
9. Counter — счётчик элементов
Подсчитывает частоту элементов в итерируемом объекте. Удобен для статистики, анализа.
from collections import Counter
text = "абракадабра"
c = Counter(text)
print(f"Счётчик букв: {c}")
# → Счётчик букв: Counter({'а': 5, 'б': 2, 'р': 2, 'к': 1, 'д': 1})
print(f"Частота 'а': {c['а']}")
# → Частота 'а': 5
print(f"Топ-3: {c.most_common(3)}")
# → Топ-3: [('а', 5), ('б', 2), ('р', 2)]
c2 = Counter("буква")
c.update(c2)
print(f"После update: {c}")
# → После update: Counter({'а': 6, 'б': 3, 'р': 2, 'к': 2, 'д': 1, 'у': 1, 'в': 1})
💡 Полезен для анализа текстов, логов, голосований — везде, где нужно посчитать, «что встречается чаще».
10. defaultdict — словарь со значениями по умолчанию
Автоматически создаёт значение по умолчанию при отсутствии ключа. Убирает необходимость проверок if key in dict.
from collections import defaultdict
dd_list = defaultdict(list)
dd_list["fruits"].append("яблоко")
dd_list["fruits"].append("банан")
print(f"Список: {dict(dd_list)}")
# → Список: {'fruits': ['яблоко', 'банан']}
dd_int = defaultdict(int)
for char in "абракадабра":
dd_int[char] += 1
print(f"Счётчик: {dict(dd_int)}")
# → Счётчик: {'а': 5, 'б': 2, 'р': 2, 'к': 1, 'д': 1}
dd_set = defaultdict(set)
dd_set["cities"].add("Москва")
dd_set["cities"].add("СПб")
print(f"Множество: {dict(dd_set)}")
# → Множество: {'cities': {'Москва', 'СПб'}}
💡 Убирает шаблонный код вида
if key not in d: d[key] = []. Делает код чище и безопаснее.
11. dataclass — классы данных
Декоратор для автоматического создания классов с инициализатором, строковым представлением, сравнением и т.д.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
city: str = "Не указан"
alice = Person("Алиса", 25)
print(f"Объект: {alice}")
# → Объект: Person(name='Алиса', age=25, city='Не указан')
print(f"Имя: {alice.name}")
# → Имя: Алиса
alice.age = 26
print(f"После изменения: {alice}")
# → После изменения: Person(name='Алиса', age=26, city='Не указан')
bob = Person("Боб", 30)
print(f"Алиса == Боб: {alice == bob}")
# → Алиса == Боб: False
@dataclass(frozen=True)
class ImmutablePerson:
name: str
age: int
ivan = ImmutablePerson("Иван", 40)
# ivan.age = 41 → FrozenInstanceError
💡 Заменяет ручное написание
__init__,__repr__,__eq__. Идеален для DTO, конфигов, моделей.
12. UserList — кастомные списки
Наследуется от collections.UserList. Используется, чтобы создать список с переопределённым поведением.
from collections import UserList
class LoggingList(UserList):
def append(self, item):
print(f"[LOG] Добавление: {item}")
super().append(item)
def remove(self, item):
print(f"[LOG] Удаление: {item}")
super().remove(item)
log_list = LoggingList([1, 2, 3])
print(f"Исходный: {log_list}")
# → Исходный: [1, 2, 3]
log_list.append(4)
# → [LOG] Добавление: 4
print(f"После append: {log_list}")
# → После append: [1, 2, 3, 4]
log_list.remove(2)
# → [LOG] Удаление: 2
print(f"После remove: {log_list}")
# → После remove: [1, 3, 4]
💡 Полезен, когда нужно добавить логирование, валидацию, или изменить поведение стандартного списка.
13. UserDict — кастомные словари
Наследуется от collections.UserDict. Используется, чтобы создать словарь с переопределённым поведением.
from collections import UserDict
class LowerKeyDict(UserDict):
def __setitem__(self, key, value):
key = key.lower() if isinstance(key, str) else key
super().__setitem__(key, value)
def __getitem__(self, key):
key = key.lower() if isinstance(key, str) else key
return super().__getitem__(key)
ld = LowerKeyDict()
ld["Name"] = "Алиса"
print(f"Значение по 'Name': {ld['Name']}")
# → Значение по 'Name': Алиса
print(f"Значение по 'name': {ld['name']}")
# → Значение по 'name': Алиса
print(f"Ключи: {list(ld.keys())}")
# → Ключи: ['name']
💡 Используется для создания словарей с нормализацией ключей, валидацией, логированием, кешированием и т.п.
📈 Сравнение коллекций по памяти и скорости
Выбор коллекции влияет на производительность и потребление памяти. Ниже — практические замеры для типичных сценариев.
1. Память: list vs tuple vs array.array
Создадим коллекции из 1 000 000 целых чисел и сравним размер в памяти.
import sys
from array import array
n = 1_000_000
data = list(range(n)) # list
data_t = tuple(range(n)) # tuple
data_a = array('i', range(n)) # array.array
print(f"list: {sys.getsizeof(data) / 1024 / 1024:.2f} MB")
# → list: 8.00 MB (примерно)
print(f"tuple: {sys.getsizeof(data_t) / 1024 / 1024:.2f} MB")
# → tuple: 8.00 MB (почти как list)
print(f"array: {sys.getsizeof(data_a) / 1024 / 1024:.2f} MB")
# → array: 3.81 MB
💡 Вывод:
array.arrayэкономит ~2x памяти для чисел.listиtupleпотребляют примерно одинаково, ноtupleнемного быстрее при итерации.
2. Скорость доступа: list vs tuple vs array.array
Замерим время доступа к каждому элементу в коллекции из 10 000 000 элементов.
import time
def time_access(collection, name):
start = time.perf_counter()
total = 0
for i in range(len(collection)):
total += collection[i]
end = time.perf_counter()
print(f"{name}: {end - start:.4f} секунд")
n = 10_000_000
lst = list(range(n))
tpl = tuple(range(n))
arr = array('i', range(n))
time_access(lst, "list")
# → list: 1.2000 секунд (примерно)
time_access(tpl, "tuple")
# → tuple: 1.0000 секунд (быстрее list)
time_access(arr, "array")
# → array: 0.8000 секунд (самый быстрый)
💡 Вывод:
array.array— самый быстрый для чисел.tupleбыстрееlistна 10–20%.- Разница заметна на больших объёмах.
3. Память: dict vs SimpleNamespace vs dataclass
Создадим объекты с 5 полями и сравним размер.
from types import SimpleNamespace
from dataclasses import dataclass
@dataclass
class PersonDC:
name: str
age: int
city: str
hobby: str
job: str
# Создаём экземпляры
d = {"name": "A", "age": 25, "city": "X", "hobby": "Y", "job": "Z"}
ns = SimpleNamespace(name="A", age=25, city="X", hobby="Y", job="Z")
dc = PersonDC("A", 25, "X", "Y", "Z")
print(f"dict: {sys.getsizeof(d)} байт")
# → dict: 232 байт
print(f"SimpleNamespace: {sys.getsizeof(ns)} байт")
# → SimpleNamespace: 64 байт (только служебные данные; атрибуты хранятся в __dict__)
print(f"dataclass: {sys.getsizeof(dc)} байт")
# → dataclass: 64 байт (аналогично)
# Но учитываем __dict__:
print(f"ns.__dict__: {sys.getsizeof(ns.__dict__)} байт")
# → ns.__dict__: 232 байт
print(f"dc.__dict__: {sys.getsizeof(dc.__dict__)} байт")
# → dc.__dict__: 232 байт
💡 Вывод:
SimpleNamespaceиdataclassпотребляют столько же памяти, сколькоdict, потому что используют__dict__.- Если нужна экономия — используйте
__slots__(см. ниже).
4. Экономия памяти: dataclass с __slots__
@dataclass
class PersonSlots:
__slots__ = ("name", "age", "city", "hobby", "job")
name: str
age: int
city: str
hobby: str
job: str
dc_slots = PersonSlots("A", 25, "X", "Y", "Z")
print(f"dataclass + slots: {sys.getsizeof(dc_slots)} байт")
# → dataclass + slots: 80 байт (без __dict__)
# Попытка добавить новый атрибут:
# dc_slots.new = "value" → AttributeError
💡 Вывод:
__slots__экономит память и ускоряет доступ к атрибутам.- Цена — нельзя добавлять новые атрибуты динамически.
5. Скорость поиска: list vs set
Проверим, насколько быстрее set при проверке вхождения.
import time
n = 1_000_000
lst = list(range(n))
st = set(range(n))
def time_in(collection, target, name):
start = time.perf_counter()
for _ in range(1000):
_ = target in collection
end = time.perf_counter()
print(f"{name} (поиск {target}): {end - start:.4f} секунд")
time_in(lst, 999_999, "list")
# → list (поиск 999999): 10.0000 секунд (линейный поиск)
time_in(st, 999_999, "set")
# → set (поиск 999999): 0.0005 секунд (хеш-таблица)
💡 Вывод:
setв тысячи раз быстрееlistдля проверки вхождения.- Всегда используй
set, если нужно часто проверятьx in collection.
6. Память: set vs frozenset
s = set(range(1000))
fs = frozenset(range(1000))
print(f"set: {sys.getsizeof(s)} байт")
# → set: 32792 байт
print(f"frozenset: {sys.getsizeof(fs)} байт")
# → frozenset: 32792 байт
💡 Вывод:
frozensetиsetпотребляют одинаково.- Разница только в изменяемости.
7. Скорость добавления: list.append vs deque.append vs deque.appendleft
from collections import deque
import time
def time_append(collection, n, method='append'):
start = time.perf_counter()
for i in range(n):
if method == 'appendleft' and hasattr(collection, 'appendleft'):
collection.appendleft(i)
else:
collection.append(i)
end = time.perf_counter()
return end - start
n = 100_000
lst = []
dq = deque()
time_list_append = time_append(lst, n)
time_deque_append = time_append(dq, n)
time_deque_appendleft = time_append(deque(), n, 'appendleft')
print(f"list.append: {time_list_append:.4f} сек")
# → list.append: 0.0100 сек
print(f"deque.append: {time_deque_append:.4f} сек")
# → deque.append: 0.0100 сек
print(f"deque.appendleft: {time_deque_appendleft:.4f} сек")
# → deque.appendleft: 0.0100 сек
# А теперь попробуем list.insert(0, x) — очень медленно!
lst = []
start = time.perf_counter()
for i in range(n):
lst.insert(0, i) # O(n) на каждую вставку!
end = time.perf_counter()
print(f"list.insert(0): {end - start:.4f} сек")
# → list.insert(0): 5.0000 сек (квадратичное время!)
💡 Вывод:
deque.appendleftработает за O(1), в отличие отlist.insert(0), который O(n).- Для частых операций на обоих концах — только
deque.
🧠 Общие рекомендации по производительности
| Ситуация | Что выбрать | Почему |
|---|---|---|
| Хранение чисел, важна память | array.array | В 2x меньше памяти, быстрее доступ |
| Данные не меняются | tuple | Быстрее list, безопаснее |
Частая проверка x in collection | set / frozenset | O(1) против O(n) у list |
| Операции на концах | deque | appendleft/popleft за O(1) |
| Структурированные данные, память критична | dataclass + __slots__ | Без __dict__, меньше памяти |
| Подсчёт частот | Counter | Оптимизирован под эту задачу |
| Кастомное поведение | UserList / UserDict | Безопасное расширение стандартных коллекций |
📊 Сравнение коллекций
| Тип | Упорядочен | Изменяем | Уникальные элементы | Доступ по индексу | Дубликаты |
|---|---|---|---|---|---|
list | ✅ Да | ✅ Да | ❌ Нет | ✅ Да | ✅ Да |
tuple | ✅ Да | ❌ Нет | ❌ Нет | ✅ Да | ✅ Да |
dict | ✅ Да* | ✅ Да | Ключи — да | ❌ Нет | Значения — да |
set | ❌ Нет | ✅ Да | ✅ Да | ❌ Нет | ❌ Нет |
frozenset | ❌ Нет | ❌ Нет | ✅ Да | ❌ Нет | ❌ Нет |
SimpleNamespace | ✅ Да (атрибуты) | ✅ Да | ❌ Нет (атрибуты могут повторяться) | ❌ Нет | ✅ Да |
namedtuple | ✅ Да | ❌ Нет | ❌ Нет | ✅ Да | ✅ Да |
deque | ✅ Да | ✅ Да | ❌ Нет | ✅ Да | ✅ Да |
Counter | ❌ Нет | ✅ Да | ❌ Нет | ❌ Нет (но есть ключи) | ✅ Да |
defaultdict | ✅ Да* | ✅ Да | Ключи — да | ❌ Нет | Значения — да |
dataclass | ✅ Да (поля) | ✅ Да (если не frozen) | ❌ Нет | ❌ Нет | ✅ Да |
UserList | ✅ Да | ✅ Да | ❌ Нет | ✅ Да | ✅ Да |
UserDict | ✅ Да* | ✅ Да | Ключи — да | ❌ Нет | Значения — да |
str | ✅ Да | ❌ Нет | ❌ Нет | ✅ Да | ✅ Да |
bytes | ✅ Да | ❌ Нет | ❌ Нет | ✅ Да | ✅ Да |
bytearray | ✅ Да | ✅ Да | ❌ Нет | ✅ Да | ✅ Да |
range | ✅ Да | ❌ Нет | ❌ Нет | ✅ Да | ❌ Нет |
array.array | ✅ Да | ✅ Да | ❌ Нет | ✅ Да | ✅ Да |
ChainMap | ✅ Да* | ✅ Да | Ключи — да | ❌ Нет | Значения — да |
Enum | ✅ Да | ❌ Нет | ✅ Да (элементы) | ❌ Нет | ❌ Нет |
- — с Python 3.7+ словари,
defaultdict,UserDict,ChainMapсохраняют порядок вставки.
💡 Когда что использовать?
| Задача | Коллекция |
|---|---|
| Хранить последовательность с изменениями | list / deque / UserList / bytearray |
| Зафиксировать данные, защитить от изменений | tuple / namedtuple / frozenset / str / bytes |
| Быстрый поиск, удаление дубликатов | set / frozenset |
| Хранить структурированные данные | dict / dataclass / SimpleNamespace / UserDict / TypedDict |
| Использовать множество как ключ | frozenset |
| Временная структура с доступом через точку | SimpleNamespace / dataclass |
| Подсчёт частот | Counter |
| Автоматическое создание значений по умолчанию | defaultdict |
| Эффективные операции на концах | deque |
| Кастомное поведение списка | UserList |
| Кастомное поведение словаря | UserDict |
| Работа с бинарными данными | bytes / bytearray / array.array |
| Конфиги с иерархией | ChainMap |
| Именованные константы | Enum |
| Ленивые последовательности | range / генераторы |