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

Контекстный менеджер для работы с файлом

1.0 Junior🔥 191 комментариев
#Python Core

Условие

Реализуйте контекстный менеджер для работы с файлом без использования встроенного open().

Используйте методы enter и exit.

Пример использования

with FileManager("test.txt", "w") as f:
    f.write("Hello, World!")

Дополнительно

Реализуйте то же самое с помощью @contextmanager из contextlib.

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

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

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

Решение: Контекстный Менеджер для Работы с Файлом

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

Подход 1: Класс с enter и exit

class FileManager:
    def __init__(self, filename: str, mode: str = 'r'):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        print(f"Opening file: {self.filename}")
        self.file = open(self.filename, self.mode)
        return self.file  # Это становится переменной после 'as'
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Closing file: {self.filename}")
        if self.file:
            self.file.close()
        # Возвращаем False, чтобы пробросить исключение дальше
        return False

# Использование
with FileManager("test.txt", "w") as f:
    f.write("Hello, World!")

Вывод:

Opening file: test.txt
Closing file: test.txt

Параметры exit

__exit__(self, exc_type, exc_val, exc_tb) получает три параметра:

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

Обработка Исключений

class FileManager:
    def __init__(self, filename: str, mode: str = 'r'):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        try:
            self.file = open(self.filename, self.mode)
            return self.file
        except IOError as e:
            print(f"Failed to open file: {e}")
            raise
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f"Error in context: {exc_type.__name__}: {exc_val}")
        
        if self.file and not self.file.closed:
            self.file.close()
        
        return False  # Пробросить исключение

# Использование с ошибкой
try:
    with FileManager("test.txt", "w") as f:
        f.write("test")
        raise ValueError("Intentional error")
except ValueError as e:
    print(f"Caught: {e}")

Подход 2: Декоратор @contextmanager

@contextmanager из модуля contextlib позволяет создавать менеджеры через генератор:

from contextlib import contextmanager

@contextmanager
def file_manager(filename: str, mode: str = 'r'):
    print(f"Opening file: {filename}")
    file = open(filename, mode)
    
    try:
        yield file  # Это становится переменной после 'as'
    finally:
        print(f"Closing file: {filename}")
        file.close()

# Использование
with file_manager("test.txt", "w") as f:
    f.write("Hello, World!")

Структура @contextmanager:

@contextmanager
def my_manager():
    # Код перед блоком with (__enter__)
    resource = acquire_resource()
    try:
        yield resource  # Результат становится переменной в 'as'
    finally:
        # Код после блока with (__exit__)
        release_resource(resource)

Сравнение Подходов

# Подход 1: Класс (явный, более гибкий)
class MyContext:
    def __enter__(self):
        print("Enter")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exit")
        return False

with MyContext() as ctx:
    print("Inside")

# Подход 2: Генератор (компактный, удобный)
from contextlib import contextmanager

@contextmanager
def my_context():
    print("Enter")
    try:
        yield None
    finally:
        print("Exit")

with my_context() as ctx:
    print("Inside")

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

1. Таймер с контекстным менеджером:

import time
from contextlib import contextmanager

@contextmanager
def timer(name: str):
    start = time.time()
    print(f"Starting: {name}")
    try:
        yield
    finally:
        elapsed = time.time() - start
        print(f"Finished: {name} ({elapsed:.2f}s)")

with timer("slow operation"):
    time.sleep(1)

2. Временное изменение настроек:

from contextlib import contextmanager

@contextmanager
def temp_settings(debug=True):
    old_debug = globals().get('DEBUG')
    globals()['DEBUG'] = debug
    try:
        yield
    finally:
        if old_debug is None:
            globals().pop('DEBUG', None)
        else:
            globals()['DEBUG'] = old_debug

3. Блокировка ресурса:

from contextlib import contextmanager
import threading

lock = threading.Lock()

@contextmanager
def acquire_lock():
    lock.acquire()
    print("Lock acquired")
    try:
        yield
    finally:
        lock.release()
        print("Lock released")

with acquire_lock():
    print("Critical section")

Встроенные Контекстные Менеджеры

Python имеет много встроенных контекстных менеджеров:

# Файлы
with open('file.txt') as f:
    content = f.read()

# Блокировки
import threading
lock = threading.Lock()
with lock:
    # критическая секция
    pass

# Временное подавление исключений
from contextlib import suppress
with suppress(FileNotFoundError):
    os.remove('file.txt')  # Не упадёт, если файла нет

# Redirect вывода
from contextlib import redirect_stdout
with redirect_stdout(file):
    print("Goes to file")  # Вместо stdout

Рекомендация

Для собеседования:

  1. Класс с enter/exit — если нужен полный контроль
  2. @contextmanager — если логика простая и используется try/finally

Когда использовать контекстные менеджеры:

  • Открытие/закрытие файлов
  • Захват/освобождение блокировок
  • Соединения с БД
  • Управление памятью
  • Временные изменения состояния

Контекстные менеджеры гарантируют, что __exit__ всегда вызовется, даже при исключениях. Это критично для надёжного управления ресурсами.