← Назад к вопросам

Что нужно для реализации контекстного менеджера?

2.0 Middle🔥 121 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Контекстный менеджер в Python: with statement и управление ресурсами

Контекстный менеджер — это объект, который управляет входом и выходом из блока кода, обычно используется для управления ресурсами (файлы, соединения, блокировки). В Python это реализуется через протокол __enter__ и __exit__.

Основы: Протокол контекстного менеджера

Для создания контекстного менеджера нужно реализовать два метода:

class MyContextManager:
    def __enter__(self):
        # Выполняется когда входим в блок with
        print("Entering context")
        # Обычно здесь инициализируем ресурсы
        return self  # Или какой-то объект
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        # Выполняется когда выходим из блока with
        # Даже если была ошибка!
        print("Exiting context")
        # Параметры:
        # - exc_type: тип исключения (None если не было)
        # - exc_val: значение исключения
        # - exc_tb: traceback исключения
        
        # Если вернуть True → исключение подавляется
        # Если False или ничего → исключение пробрасывается дальше
        return False

# Использование
with MyContextManager() as cm:
    print("Inside context")
    # Автоматически вызовется __exit__ даже при ошибке

# Вывод:
# Entering context
# Inside context
# Exiting context

Практические примеры

1. Управление файлом (самый частый случай)

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

# Эквивалентно:
try:
    f = open("data.txt", "r")
    content = f.read()
finally:
    f.close()  # Гарантировано вызовется

# Собственная реализация:
class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        return False

# Использование:
with FileManager("data.txt", "r") as f:
    content = f.read()

2. Управление подключением к БД

class DatabaseConnection:
    def __init__(self, connection_string):
        self.connection_string = connection_string
        self.conn = None
    
    def __enter__(self):
        # Подключаемся при входе
        self.conn = sqlite3.connect(self.connection_string)
        return self.conn
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        # Закрываемся при выходе
        if self.conn:
            self.conn.close()
        return False

# Использование:
with DatabaseConnection("db.sqlite") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    results = cursor.fetchall()
# Соединение автоматически закроется

3. Управление блокировкой (thread lock)

import threading

lock = threading.Lock()

# Lock имеет __enter__ и __exit__
with lock:
    # Критичный раздел кода
    shared_resource.value = 42
# Lock автоматически отпустится

# Собственная реализация:
class Lock:
    def __init__(self):
        self._locked = False
    
    def __enter__(self):
        # Попробовать захватить блокировку
        while self._locked:
            pass  # Ждём
        self._locked = True
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        # Освободить блокировку
        self._locked = False
        return False

4. Управление временем и логированием

import time

class Timer:
    def __init__(self, name):
        self.name = name
        self.start = None
    
    def __enter__(self):
        self.start = time.time()
        print(f"Starting {self.name}")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        elapsed = time.time() - self.start
        print(f"Finished {self.name} in {elapsed:.2f}s")
        return False

# Использование:
with Timer("database query"):
    result = db.execute("SELECT * FROM large_table")
    # Выведет время выполнения

5. Обработка исключений в контекстном менеджере

class ExceptionHandler:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            # Произошла ошибка
            print(f"Error: {exc_type.__name__}: {exc_val}")
            # Вернуть True → подавить исключение
            # Вернуть False → пробросить исключение
            return True  # Подавляем ошибку
        return False

# Использование:
with ExceptionHandler():
    x = 1 / 0  # ZeroDivisionError
    # Error напечатается, но код продолжит выполняться

print("Code continued after error")  # Это выполнится

contextlib.contextmanager — декоратор

Вместо класса с enter и exit, можно использовать декоратор:

from contextlib import contextmanager

@contextmanager
def simple_context():
    print("Entering")
    yield  # Код перед yield выполняется при __enter__
    # Код после yield выполняется при __exit__
    print("Exiting")

with simple_context():
    print("Inside")

# Более сложный пример:
@contextmanager
def database_transaction(connection):
    cursor = connection.cursor()
    try:
        yield cursor  # Передаём курсор в блок with
        connection.commit()  # Успех → коммитим
    except Exception as e:
        connection.rollback()  # Ошибка → откатываем
        raise
    finally:
        cursor.close()

with database_transaction(conn) as cursor:
    cursor.execute("INSERT INTO users VALUES (...)")

Множественные контекстные менеджеры

# Python 3.10+ синтаксис (или несколько as)
with open("input.txt") as fin, open("output.txt", "w") as fout:
    for line in fin:
        fout.write(line.upper())

# Или старший синтаксис:
with open("input.txt") as fin:
    with open("output.txt", "w") as fout:
        for line in fin:
            fout.write(line.upper())

# Или с использованием ExitStack:
from contextlib import ExitStack

with ExitStack() as stack:
    fin = stack.enter_context(open("input.txt"))
    fout = stack.enter_context(open("output.txt", "w"))
    for line in fin:
        fout.write(line.upper())

Параметры exit

class DetailedContext:
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            print("No error occurred")
        elif exc_type is ValueError:
            print(f"ValueError: {exc_val}")
            return True  # Подавляем ValueError
        elif exc_type is KeyError:
            print(f"KeyError: {exc_val}")
            return False  # Не подавляем KeyError
        else:
            # Другие ошибки
            return False

# exc_tb содержит traceback
# Можно использовать для логирования полного стека:
print(exc_tb.tb_frame)  # Информация о фрейме где произошла ошибка

Лучшие практики

# ✅ Хорошо: используй контекстные менеджеры для управления ресурсами
with open("file.txt") as f:
    data = f.read()

# ❌ Плохо: открыть, забыть закрыть
f = open("file.txt")
data = f.read()
# Файл останется открытым!

# ✅ Хорошо: явное управление ошибками
with lock:
    critical_section()

# ❌ Плохо: попробуй/отпусти вручную
lock.acquire()
try:
    critical_section()
finally:
    lock.release()

Заключение

Контекстный менеджер — это мощный инструмент для управления ресурсами в Python. Реализуется через методы __enter__ и __exit__, обеспечивает гарантированное освобождение ресурсов даже при ошибках. Используется для файлов, соединений БД, блокировок и других ресурсов. В современном Python предпочитай контекстные менеджеры явным управлению ресурсами.

Что нужно для реализации контекстного менеджера? | PrepBro