Что такое контекстный менеджер в Python?

Ответ

Контекстный менеджер в Python — это объект, который определяет контекст выполнения для блока кода с помощью методов __enter__() и __exit__(). Он гарантирует, что определенные операции (например, выделение и освобождение ресурсов) будут выполнены до и после этого блока, даже если внутри возникло исключение. Используется с оператором with.

Зачем это нужно? Для безопасного управления ресурсами (файлы, сетевые соединения, транзакции БД, блокировки), чтобы избежать утечек и обеспечить корректное завершение.

Пример использования встроенного контекстного менеджера для работы с файлами:

# Без контекстного менеджера (потенциально небезопасно)
f = open('file.txt', 'r')
try:
    data = f.read()
finally:
    f.close()  # Необходимо не забыть закрыть

# С контекстным менеджером (рекомендуемый способ)
with open('file.txt', 'r') as f:
    data = f.read()
# Файл автоматически закрыт здесь, даже если при чтении возникла ошибка

Создание собственного контекстного менеджера:

  1. Как класс:

    class Timer:
        def __init__(self, name):
            self.name = name
        def __enter__(self):
            self.start = time.time()
            return self  # Объект, который будет связан с переменной после 'as'
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.end = time.time()
            print(f'{self.name}: {self.end - self.start:.2f} сек')
    
    with Timer('Мой код') as t:
        time.sleep(1)
    # Вывод: Мой код: 1.00 сек
  2. С использованием contextlib.contextmanager (как генератор):

    from contextlib import contextmanager
    import time
    
    @contextmanager
    def timer(name):
        start = time.time()
        yield  # Выполнение переходит в блок 'with'
        end = time.time()
        print(f'{name}: {end - start:.2f} сек')
    
    with timer('Замер'):
        time.sleep(0.5)

Ответ 18+ 🔞

А, ну это же про контекстные менеджеры в питоне! Ёпта, вещь-то архиполезная, а многие её как мантру читают, не вникая. Сейчас разжуём, блядь.

Представь себе, что у тебя есть ресурс — ну, файл, сетевое соединение, какая-нибудь блокировка. И его надо обязательно закрыть, отпустить, откатить — короче, прибрать за собой. А если забудешь — будет тебе утечка памяти, сокеты висеть, файлы заблокированы. Доверия ебать ноль к самому себе, что не забудешь.

Вот раньше-то писали так, ёбаный насос:

f = open('file.txt', 'r')
try:
    data = f.read()
finally:
    f.close()  # Необходимо не забыть закрыть

Смотри, какая хитрая жопа: открыл файл, потом try/finally на всякий пожарный, чтобы даже если ошибка — файл закрылся. Громоздко, сука, можно и забыть. А питон — он для ленивых, блядь. Поэтому придумали оператор with.

Тот же самый процесс, но в разы короче и надёжнее:

with open('file.txt', 'r') as f:
    data = f.read()
# Файл автоматически закрыт здесь, даже если при чтении возникла ошибка

Вот это, блядь, красота! Всё. Ты как бы говоришь: "Заходи в контекст (with), работай с файлом f, а как выйдешь — я сам всё закрою, не парься". И не важно, вылетело ли исключение посередине или нет — файл закроется. Волнение ебать на нуле.

А если хочешь свой такой шлюз придумать? Ну, например, замер времени выполнения блока. Овердохуища просто. Есть два пути.

Первый — через класс. Надо сделать методы __enter__ и __exit__.

class Timer:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        self.start = time.time()
        return self  # Объект, который будет связан с переменной после 'as'
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end = time.time()
        print(f'{self.name}: {self.end - self.start:.2f} сек')

with Timer('Мой код') as t:
    time.sleep(1)
# Вывод: Мой код: 1.00 сек

Смотри, как чётко: заходишь в with — вызывается __enter__, засекается время. Выходишь — вызывается __exit__, выводится результат. Если внутри блока пиздец случится (исключение), эти параметры exc_type, exc_val, exc_tb тебе про него расскажут. Можешь их обработать или проигнорировать — да похуй.

Второй способ — через декоратор @contextmanager. Это для тех, кому терпения ноль ебать городить целый класс.

from contextlib import contextmanager
import time

@contextmanager
def timer(name):
    start = time.time()
    yield  # Выполнение переходит в блок 'with'
    end = time.time()
    print(f'{name}: {end - start:.2f} сек')

with timer('Замер'):
    time.sleep(0.5)

Вот это, блядь, ебушки-воробушки! Всё то же самое, но в виде функции-генератора. Всё до yield — это аналог __enter__. Сам yield — это место, где управление передаётся в твой блок кода. А всё после yield — это __exit__, который выполнится при выходе.

Короче, суть в чём: не доверяй себе, что ты всё правильно закроешь. Доверь это питону через with. И живи спокойно, я тебя в сраку чих-пых.