Работали ли вы с контекстными менеджерами (context managers) в Python?

Ответ

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

Стандартное использование с файлами и сетевыми ресурсами:

# Автоматическое закрытие файла
with open('data.json', 'r') as f:
    data = json.load(f)
# Файл закрыт здесь, даже если внутри блока было исключение

# Работа с сессиями requests
with requests.Session() as session:
    session.auth = ('user', 'pass')
    response = session.get('https://api.example.com/data')
# Сессия корректно закрыта

Создание собственного контекстного менеджера с помощью класса:

import sqlite3
from contextlib import contextmanager

class DatabaseConnection:
    def __init__(self, db_path):
        self.db_path = db_path
        self.conn = None

    def __enter__(self):
        self.conn = sqlite3.connect(self.db_path)
        self.conn.execute('PRAGMA foreign_keys = ON')  # Пример настройки
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.conn.commit()  # Коммит, если не было ошибок
        else:
            self.conn.rollback()  # Откат при ошибке
        self.conn.close()

# Использование
with DatabaseConnection('app.db') as conn:
    cursor = conn.cursor()
    cursor.execute('INSERT INTO users (name) VALUES (?)', ('Alice',))
# Соединение гарантированно закрыто, транзакция завершена

Использование декоратора @contextmanager из contextlib:

from contextlib import contextmanager
import time

@contextmanager
def timer(operation_name):
    """Контекстный менеджер для замера времени выполнения."""
    start = time.perf_counter()
    try:
        yield
    finally:
        end = time.perf_counter()
        print(f"{operation_name} took {end - start:.4f} seconds")

with timer("Data processing"):
    # Долгая операция
    process_large_dataset()

В реальных проектах я применял контекстные менеджеры для управления пулами соединений с БД, временной смены текущей директории (contextlib.chdir), а также для реализации механизма блокировок (lock) при работе с многопоточным кодом.

Ответ 18+ 🔞

Ну, ты представляешь, в Питоне эта штука — контекстные менеджеры — это просто спасение, ёпта. Как будто тебе дали личного раба, который за тобой всё приберёт, даже если ты посередине операции словишь исключение и начнёшь орать как резаный. Без них — пиздец, постоянно следи, закрыл ли ты файл, откатил ли транзакцию... А с ними — красота, with написал и забыл.

Вот как это обычно выглядит, когда работаешь с файлами или сетью:

# Файл сам закроется, даже если тебя тут током шарахнет
with open('data.json', 'r') as f:
    data = json.load(f)
# Всё, уже закрыт. Никаких утечек, всё чисто.

# Или вот сессия requests — тоже самое
with requests.Session() as session:
    session.auth = ('user', 'pass')
    response = session.get('https://api.example.com/data')
# Сессия прибита, ресурсы освобождены. Удобно, блядь.

А если надо свой, кастомный, накрутить — пожалуйста, делаем класс:

import sqlite3
from contextlib import contextmanager

class DatabaseConnection:
    def __init__(self, db_path):
        self.db_path = db_path
        self.conn = None

    def __enter__(self):
        # Вот тут мы входим в контекст, создаём соединение
        self.conn = sqlite3.connect(self.db_path)
        self.conn.execute('PRAGMA foreign_keys = ON')  # Какая-нибудь настройка
        return self.conn  # И отдаём его наружу

    def __exit__(self, exc_type, exc_val, exc_tb):
        # А тут выходим. Это святое — что бы ни случилось, соединение закроется.
        if exc_type is None:
            self.conn.commit()  # Если всё ок — коммитим
        else:
            self.conn.rollback()  # Если была ошибка — откатываем, ёбана
        self.conn.close()  # И в любом случае — нахуй закрываем

# Используем
with DatabaseConnection('app.db') as conn:
    cursor = conn.cursor()
    cursor.execute('INSERT INTO users (name) VALUES (?)', ('Alice',))
# Всё, блядь. Соединение убито, транзакция завершена. Спи спокойно.

Но есть способ проще, для ленивых — декоратор @contextmanager:

from contextlib import contextmanager
import time

@contextmanager
def timer(operation_name):
    """Засекаем время выполнения. Проще пареной репы."""
    start = time.perf_counter()
    try:
        yield  # Вот тут выполняется твой код внутри `with`
    finally:
        end = time.perf_counter()
        print(f"{operation_name} заняло {end - start:.4f} секунд")

with timer("Обработка данных"):
    # Какая-нибудь тяжёлая хуйня
    process_large_dataset()
# И тебе на блюдечке выдали время. Красота, ядрёна вошь.

В реальных проектах я этой хуйней пользовался на овердохуища. Например, для пулов соединений к базе — чтобы не пиздеть лишние подключения. Или для временной смены рабочей директории — сделал with chdir('/tmp'):, поработал и вернулся обратно, даже думать не надо. Ну и для всяких блокировок в многопоточке — зашёл в контекст, взял лок, поработал с общими данными, вышел — лок отпустился сам. Вообще, вещь, блядь, незаменимая.