💻 Примеры

⚡ Кратко: что важно

from collections import Counter
votes = ["red", "blue", "red"]
print(Counter(votes).most_common(1))
# [('red', 2)]
ИнструментДля чего
Counterподсчёт частоты, most_common()
dequeбыстрое добавление/удаление с обоих концов
defaultdictсловарь со значением по умолчанию для новых ключей
@lru_cache(...)кэширует результаты вызовов функции
frozensetнеизменяемое множество — годится ключом словаря
Топ-3 ошибки: func() вместо ссылки func · забытый list() вокруг map/filter · обычный set нехешируем — ключом словаря не станет.

Пример 1. Измерение времени

Сравним скорость создания объекта range и списка.

import time

start = time.time()
values = range(1_000_000)
elapsed_range = time.time() - start

start = time.time()
values_list = list(range(1_000_000))
elapsed_list = time.time() - start

print(f"range: создаётся мгновенно (лениво)")
print(f"list:  требует заполнить память, elapsed_list > 0: {elapsed_list > 0}")
# range: создаётся мгновенно (лениво)
# list:  требует заполнить память, elapsed_list > 0: True

Что происходит: range хранит только начало, конец и шаг — числа вычисляются по требованию (ленивая последовательность). list(range(...)), наоборот, сразу создаёт миллион объектов в памяти, поэтому измеримо медленнее.

Пример 2. OrderedDict как очередь задач

OrderedDict — словарь, который явно хранит порядок вставки и добавляет методы popitem(last=False) и move_to_end().

from collections import OrderedDict

od = OrderedDict()
od["a"] = 1
od["b"] = 2
od["c"] = 3
print(od.popitem(last=False))  # первый вставленный элемент
print(od)
# ('a', 1)
# OrderedDict([('b', 2), ('c', 3)])

queue = OrderedDict([
    ("task1", "low"),
    ("task2", "high"),
    ("task3", "low"),
])

for key, value in list(queue.items()):
    if "high" in value:
        queue.move_to_end(key, last=False)
    elif "low" in value:
        queue.move_to_end(key)

print(queue)
# OrderedDict([('task2', 'high'), ('task1', 'low'), ('task3', 'low')])

Почему так: задачи с приоритетом high перемещаются в начало (last=False), задачи с low — в конец. Обычный dict тоже сохраняет порядок вставки с Python 3.7+, но не даёт этих специализированных методов.

Пример 3. LRU-кэш на практике

Декоратор @lru_cache запоминает результаты вызовов и ускоряет рекурсивное вычисление чисел Фибоначчи — без него fibonacci(35) считался бы экспоненциально долго.

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(35))
print(fibonacci.cache_info())
# 9227465
# CacheInfo(hits=33, misses=36, maxsize=128, currsize=36)

Что происходит: каждое новое значение n считается один раз и кладётся в кэш (misses); при повторном обращении к уже посчитанному n результат берётся из кэша (hits) — отсюда мгновенный ответ даже для n = 35.

Пример 4. frozenset как ключ словаря

Используем неизменяемое множество там, где обычный set не подходит — например, ключом словаря.

frozen = frozenset(["read", "write"])
permissions = {frozen: "editor"}
print(permissions[frozen])
# editor

Пример 5. Counter: подсчёт частоты элементов

Counter — специализированный словарь для подсчёта: считает, сколько раз встретился каждый элемент, и умеет отдавать самые частые.

from collections import Counter

votes = ["red", "blue", "red", "green", "blue", "red"]
tally = Counter(votes)
print(tally)
print(tally.most_common(2))
# Counter({'red': 3, 'blue': 2, 'green': 1})
# [('red', 3), ('blue', 2)]
⚠️ Проверить по документации: Counter назван в кратком описании занятия, но отдельно на занятии не разбирался — добавлен как важное дополнение к теме collections.

Пример 6. deque: быстрое добавление и удаление с обоих концов

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

from collections import deque

recent_events = deque(maxlen=3)
for event in ["login", "click", "scroll", "logout"]:
    recent_events.append(event)
print(recent_events)
# deque(['click', 'scroll', 'logout'], maxlen=3)

history = deque([1, 2, 3])
history.appendleft(0)
history.append(4)
print(history)
# deque([0, 1, 2, 3, 4])

Что происходит: у recent_events задан maxlen=3 — при добавлении четвёртого элемента ("logout") самый старый ("login") автоматически удаляется. У обычного списка такого поведения нет.

⚠️ Проверить по документации: deque назван в кратком описании занятия, но отдельно на занятии не разбирался — добавлен как важное дополнение к теме collections.

Пример 7. defaultdict: группировка без ручной проверки ключа

defaultdict создаёт значение по умолчанию при первом обращении к отсутствующему ключу — не нужно писать if key not in dict: dict[key] = [].

from collections import defaultdict

words = ["apple", "banana", "avocado", "blueberry", "cherry"]
by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)
print(dict(by_letter))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
⚠️ Проверить по документации: defaultdict назван в кратком описании занятия, но отдельно на занятии не разбирался — добавлен как важное дополнение к теме collections.

Пример 8. Основные операции со словарём

Добавление, обновление, проверка ключа и удаление.

profile = {"name": "Alice", "age": 30}
profile["city"] = "Berlin"
profile.update({"age": 31, "email": "alice@example.com"})

if "email" in profile:
    print(profile["email"])

removed = profile.pop("email")
print(f"Удалено: {removed}")
print(profile)
# alice@example.com
# Удалено: alice@example.com
# {'name': 'Alice', 'age': 31, 'city': 'Berlin'}

Пример 9. Передача функции в функцию

Функцию можно передать как аргумент и вызвать внутри обёртки — функции в Python такие же объекты, как числа и строки.

def double(x):
    return x * 2

def apply(func, data):
    return [func(x) for x in data]

print(apply(double, [1, 2, 3]))
# [2, 4, 6]

Пример 10. Lambda с сортировкой

Сортируем список кортежей по нескольким критериям сразу: сначала по убыванию возраста, затем по имени.

people = [("Mike", 19), ("Nancy", 35), ("Oscar", 33), ("Frank", 33)]

by_age = sorted(people, key=lambda p: p[1])
by_age_name = sorted(people, key=lambda p: (-p[1], p[0]))

print(by_age)
print(by_age_name)
# [('Mike', 19), ('Oscar', 33), ('Frank', 33), ('Nancy', 35)]
# [('Nancy', 35), ('Frank', 33), ('Oscar', 33), ('Mike', 19)]

Пример 11. Парадигмы программирования: функциональный подход

Python — мультипарадигменный язык: императивный, процедурный, ООП, функциональный, декларативный стили. Функциональный подход строит новый объект вместо изменения старого.

# функциональный подход: новый список вместо изменения старого
numbers = [1, 2, 3]
doubled = [x * 2 for x in numbers]
print(doubled)
# [2, 4, 6]

Пример 12. Ловушка: забытый list() и попытка изменить frozenset

Две частые ошибки: печатать сам объект-итератор вместо результата, и пытаться изменить неизменяемую коллекцию.

numbers = [1, 2, 3]
doubled_iterator = map(lambda x: x * 2, numbers)
print(doubled_iterator)          # <map object at ...> — это не список
print(list(doubled_iterator))    # только так виден результат
# <map object at 0x...>
# [2, 4, 6]

frozen = frozenset([1, 2, 3])
try:
    frozen.add(4)
except AttributeError as error:
    print(f"Ошибка: {error}")
# Ошибка: 'frozenset' object has no attribute 'add'

try:
    wrong_key = {1, 2}
    lookup = {wrong_key: "value"}
except TypeError as error:
    print(f"Ошибка: {error}")
# Ошибка: unhashable type: 'set'

Почему так: map()/filter() возвращают ленивые итераторы, а не готовые списки — печать самого объекта показывает его тип и адрес в памяти, а не значения. frozenset неизменяем — у него нет add(). Обычный set изменяем, поэтому нехешируем и не может быть ключом словаря (в отличие от frozenset из примера 4).

Проверить по документации: современные детали поведения встроенных функций, стандартной библиотеки и версии Python сверяйте с официальной документацией.