← Назад к вопросам
Как сделать свой контекстный менеджер в рамках функции в Python?
2.0 Middle🔥 91 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как сделать свой контекстный менеджер в рамках функции в Python
Контекстный менеджер — это объект, который управляет ресурсами через with выражение. Существует несколько способов создать его, включая простой вариант внутри функции с использованием декоратора.
1. Базовая концепция: протокол контекстного менеджера
class MyContextManager:
def __enter__(self):
"""Код при входе в блок with"""
print("Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Код при выходе из блока with"""
print("Exiting context")
# Возвращаем True если обработали исключение
return False
# Использование
with MyContextManager() as cm:
print("Inside context")
2. Самый простой способ: @contextmanager декоратор
from contextlib import contextmanager
@contextmanager
def my_context():
"""Контекстный менеджер в виде функции"""
# Код при входе
print("Setting up resource")
resource = "important data"
try:
yield resource # Передаём ресурс в блок with
finally:
# Код при выходе (ВСЕГДА выполняется)
print("Cleaning up resource")
# Использование
with my_context() as res:
print(f"Using: {res}")
# Output:
# Setting up resource
# Using: important data
# Cleaning up resource
3. Обработка исключений
from contextlib import contextmanager
@contextmanager
def safe_context():
print("Acquiring resource")
resource = []
try:
yield resource
except ValueError as e:
print(f"Caught ValueError: {e}")
# Можем обработать или пробросить дальше
# return True # Если вернём True, исключение не пробросится
except Exception as e:
print(f"Caught other exception: {e}")
raise # Пробрасываем дальше
finally:
print("Releasing resource")
# Использование
with safe_context() as res:
res.append(1)
raise ValueError("Something wrong")
# Output:
# Acquiring resource
# Caught ValueError: Something wrong
# Releasing resource
4. Практический пример: контекстный менеджер для БД
from contextlib import contextmanager
import sqlite3
@contextmanager
def database(db_name):
"""Контекстный менеджер для управления БД соединением"""
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
try:
print(f"Connected to {db_name}")
yield cursor # Передаём cursor в блок with
except Exception as e:
print(f"Error: {e}")
conn.rollback() # Откатываем транзакцию при ошибке
raise
finally:
conn.commit() # Коммитим изменения
conn.close() # Закрываем соединение
print(f"Disconnected from {db_name}")
# Использование
with database(':memory:') as cursor:
cursor.execute('CREATE TABLE users (id INTEGER, name TEXT)')
cursor.execute('INSERT INTO users VALUES (1, "Alice")')
cursor.execute('SELECT * FROM users')
print(cursor.fetchall())
# Output:
# Connected to :memory:
# [(1, 'Alice')]
# Disconnected from :memory:
5. Контекстный менеджер с параметрами
from contextlib import contextmanager
@contextmanager
def file_handler(filename, mode='r'):
"""Контекстный менеджер для работы с файлами"""
print(f"Opening {filename} in {mode} mode")
file = open(filename, mode)
try:
yield file
except IOError as e:
print(f"File error: {e}")
raise
finally:
print(f"Closing {filename}")
file.close()
# Использование
with file_handler('data.txt', 'w') as f:
f.write("Hello, World!")
# Output:
# Opening data.txt in w mode
# Closing data.txt
6. Контекстный менеджер для логирования времени выполнения
from contextlib import contextmanager
import time
@contextmanager
def timer(name="Operation"):
"""Контекстный менеджер для измерения времени"""
start = time.time()
print(f"[{name}] Starting...")
try:
yield
finally:
end = time.time()
duration = end - start
print(f"[{name}] Completed in {duration:.3f}s")
# Использование
with timer("Data Processing"):
time.sleep(1)
print("Processing...")
# Output:
# [Data Processing] Starting...
# Processing...
# [Data Processing] Completed in 1.001s
7. Стек контекстных менеджеров
from contextlib import contextmanager, ExitStack
@contextmanager
def resource_a():
print("Acquiring A")
try:
yield "Resource A"
finally:
print("Releasing A")
@contextmanager
def resource_b():
print("Acquiring B")
try:
yield "Resource B"
finally:
print("Releasing B")
# Использование нескольких менеджеров
with resource_a() as a, resource_b() as b:
print(f"Using {a} and {b}")
# Output:
# Acquiring A
# Acquiring B
# Using Resource A and Resource B
# Releasing B
# Releasing A
# Или динамически с ExitStack
with ExitStack() as stack:
a = stack.enter_context(resource_a())
b = stack.enter_context(resource_b())
print(f"Using {a} and {b}")
8. Контекстный менеджер для блокировок
from contextlib import contextmanager
import threading
@contextmanager
def thread_lock(lock):
"""Контекстный менеджер для управления блокировками"""
print("Acquiring lock")
lock.acquire()
try:
yield
finally:
print("Releasing lock")
lock.release()
# Использование
lock = threading.Lock()
with thread_lock(lock):
print("Critical section")
# Output:
# Acquiring lock
# Critical section
# Releasing lock
9. Контекстный менеджер для подавления исключений
from contextlib import suppress
# suppress встроенный контекстный менеджер
with suppress(ValueError, TypeError):
int('not a number') # ValueError подавляется
print("This line doesn't execute")
print("Program continues")
# Или создаём свой
from contextlib import contextmanager
@contextmanager
def ignore(*exceptions):
"""Подавляет указанные исключения"""
try:
yield
except exceptions:
pass # Игнорируем исключения
with ignore(ValueError):
int('invalid') # Не вызовет ошибку
10. Контекстный менеджер для транзакций
from contextlib import contextmanager
class SimpleDatabase:
def __init__(self):
self.data = {}
self.in_transaction = False
@contextmanager
def transaction(self):
"""Контекстный менеджер для транзакций"""
backup = self.data.copy()
self.in_transaction = True
try:
yield self
except Exception as e:
print(f"Transaction failed: {e}")
self.data = backup # Откатываем изменения
raise
finally:
self.in_transaction = False
print("Transaction complete")
def set(self, key, value):
if not self.in_transaction:
raise RuntimeError("Must use transaction context")
self.data[key] = value
# Использование
db = SimpleDatabase()
try:
with db.transaction():
db.set('name', 'Alice')
db.set('age', 30)
# raise ValueError("Something wrong") # Откатит changes
except ValueError:
pass
print(db.data)
11. Контекстный менеджер с return значением
from contextlib import contextmanager
@contextmanager
def acquire_resource():
"""Контекстный менеджер, возвращающий управляемый объект"""
class Resource:
def __init__(self):
self.state = "initialized"
def use(self):
return f"Using {self.state}"
resource = Resource()
try:
print("Resource acquired")
yield resource
finally:
print(f"Resource released (was {resource.state})")
# Использование
with acquire_resource() as res:
print(res.use())
res.state = "modified"
# Output:
# Resource acquired
# Using initialized
# Resource released (was modified)
Best Practices
# ✓ Хорошо: используем contextmanager для простых случаев
@contextmanager
def my_resource():
setup()
try:
yield resource
finally:
cleanup()
# ✗ Плохо: забыли finally
@contextmanager
def bad_resource():
setup()
yield resource
cleanup() # Не выполнится при исключении
# ✓ Хорошо: обрабатываем исключения явно
@contextmanager
def safe_resource():
try:
yield resource
except SpecificError:
handle_error()
# return или raise
finally:
cleanup() # ВСЕГДА выполнится
# ✓ Хорошо: используем multiple context managers
with manager1() as m1, manager2() as m2:
use(m1, m2)
Заключение
Контекстные менеджеры в виде функций с @contextmanager — это самый простой и читаемый способ управлять ресурсами в Python. Код до yield выполняется при входе в with, код после при выходе. Это гарантирует правильную очистку ресурсов даже при исключениях, благодаря finally блоку.