Менеджери контексту і with: безпечне відкриття й закриття ресурсів
Коротко: Оператор with гарантує, що ресурси на кшталт файлів, з’єднань чи блокувань звільняються навіть за помилок; власні менеджери контексту ви створюєте за допомогою класів або contextlib.
Докладніше: Хто відкрив файл, має його закрити, інакше дескриптори лишаються відкритими, дані незаписаними, блокування утримуваними. try/finally працює, але незручний. with робить це автоматично.
Подробиці
Шаблон
with open("data.txt", encoding="utf-8") as f:
content = f.read()
# тут файл гарантовано закритий, навіть за виняткуЕквівалентно try: ... finally: f.close(). Кілька ресурсів: with open(a) as x, open(b, "w") as y:. Типові менеджери контексту: файли, threading.Lock(), з’єднання/транзакції бази даних (with conn:), tempfile.TemporaryDirectory(), socket, zipfile, unittest.mock.patch, subprocess, HTTP-сеанси (requests.Session).
Власні менеджери контексту
Клас з __enter__ та __exit__:
import time
class Stopwatch:
def __enter__(self):
self.start = time.perf_counter(); return self
def __exit__(self, typ, value, tb):
print(f"{time.perf_counter() - self.start:.3f}s")
return False # не ковтати винятокГенератор з contextlib:
from contextlib import contextmanager
@contextmanager
def workdir(path):
old = os.getcwd(); os.chdir(path)
try:
yield
finally:
os.chdir(old)__exit__ отримує відомості про виняток; повернення True пригнічує його (рідко розумно; contextlib.suppress(FileNotFoundError) — безпечний варіант).
Інші інструменти
contextlib.closing,ExitStack(динамічно багато ресурсів),redirect_stdout.- async:
async withдля асинхронних ресурсів (пул бази даних, HTTP-клієнтaiohttp),__aenter__/__aexit__. - Файли:
Path.read_text()обгортаєwithдля однорядкових рішень. - Транзакції:
with db.transaction():автоматично commit/rollback. - Тести:
with pytest.raises(ValueError):,with patch(...). - Блокування:
with lock:запобігає забутомуrelease()(див. mutex/deadlock).
Типові помилки та поради
- Залишити файл відкритим без
with(Windows тоді не зможе його видалити). - Повернення з блоку with допустиме:
__exit__усе одно виконується. - Генераторний менеджер контексту без
try/finallyне очищає за винятку. - Не ковтайте винятки:
__exit__повертаєFalse/None. - Завжди вказуйте
encodingпід час відкриття тексту. - Витоки ресурсів виявляються через
ResourceWarning/-X dev. - Пов’язане: RAII (C++),
try-with-resources(Java),defer(Go),using(C#).
Див. також: Файли в Python, М’ютекс