6

Объяснение '__enter__' и '__exit__' в Python

35

Описание проблемы для StackOverflow:

Я увидел следующий код и не совсем понимаю, что он означает:

def __enter__(self):
    return self

def __exit__(self, type, value, tb):
    self.stream.close()

Кроме этого, вот полный код:

from __future__ import with_statement  # для python 2.5 

class a(object):
    def __enter__(self):
        print 'sss'
        return 'sss111'
    
    def __exit__(self, type, value, traceback):
        print 'ok'
        return False

with a() as s:
    print s

print s

Можете объяснить, что происходит в этом коде и какую роль играют методы __enter__ и __exit__? Почему используется конструкция with?

4 ответ(ов)

1

Если вы знаете, что такое контекстные менеджеры, то вам не нужно ничего больше, чтобы понять магические методы __enter__ и __exit__. Давайте рассмотрим очень простой пример.

В этом примере я открываю файл myfile.txt с помощью функции open. Блок try/finally гарантирует, что даже если произойдёт неожиданное исключение, файл myfile.txt будет закрыт.

fp = open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
    for line in fp:
        print(line)
finally:
    fp.close()

Теперь я открываю тот же файл с помощью оператора with:

with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
    for line in fp:
        print(line)

Если вы посмотрите на код, я не закрыл файл, и здесь нет блока try/finally. Потому что оператор with автоматически закрывает myfile.txt. Вы даже можете проверить это, вызвав атрибут print(fp.closed) — он вернёт True.

Это происходит потому, что объекты файлов (fp в моем примере), возвращаемые функцией open, имеют два встроенных метода: __enter__ и __exit__. Они также известны как контекстные менеджеры. Метод __enter__ вызывается в начале блока with, а метод __exit__ вызывается в конце.

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

Теперь давайте определим собственный класс контекстного менеджера.

class Log:
    def __init__(self, filename):
        self.filename = filename
        self.fp = None    
    def logging(self, text):
        self.fp.write(text + '\n')
    def __enter__(self):
        print("__enter__")
        self.fp = open(self.filename, "a+")
        return self    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("__exit__")
        self.fp.close()

with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile:
    print("Main")
    logfile.logging("Test1")
    logfile.logging("Test2")

Надеюсь, теперь у вас есть базовое понимание обоих магических методов __enter__ и __exit__.

1

В дополнение к приведённым выше ответам для иллюстрации порядка вызовов, вот простой пример выполнения кода:

class MyClass:
    def __init__(self):
        print("__init__")

    def __enter__(self):
        print("__enter__")

    def __exit__(self, ex_type, ex_value, ex_traceback):
        print(f"ex_type, ex_value, ex_traceback={ex_type, ex_value, ex_traceback}")
        # если возникают какие-либо ошибки, здесь можно выполнить специальную обработку
        if ex_type == KeyError:
            # повторно вызвать исключение для обработки в верхних вызывающих функциях
            return False
            # возвращая True, мы игнорируем исключение

        # все остальные случаи идут к стандартному выходу, например, для очистки ресурсов и т.д.
        print("__exit__")

    def __del__(self):
        print("__del__")

with MyClass():
    print("body")

    # # раскомментируйте, чтобы смоделировать специальные исключения, например KeyError для обработки в __exit__()
    # raise KeyError("key error")

В результате выполнения вы получите следующий вывод:

__init__
__enter__
body
ex_type, ex_value, ex_traceback=(None, None, None)
__exit__
__del__

Обратите внимание: при использовании синтаксиса with MyClass() as my_handler, переменная my_handler получит значение, возвращаемое методом __enter__(), в данном случае это None. Для таких случаев рекомендуется определить возвращаемое значение, например:

def __enter__(self): 
    print('__enter__')
    return self
0

Это называется менеджер контекста, и я хотел бы добавить, что аналогичные подходы существуют и в других языках программирования. Сравнение этих подходов может помочь лучше понять менеджер контекста в Python.

В общем, менеджер контекста используется, когда мы работаем с ресурсами (файлы, сеть, база данных), которые нужно инициализировать, а затем, в какой-то момент, освободить (осуществить утилизацию). В Java 7 и выше у нас есть автоматическое управление ресурсами, которое принимает следующую форму:

// Java код
try (Session session = new Session()) {
    // делаем что-то
}

Обратите внимание, что Session должен реализовывать интерфейс AutoClosable или один из его (многих) подинтерфейсов.

В C# у нас есть операторы using для управления ресурсами, которые принимают следующую форму:

// C# код
using(Session session = new Session()) {
    // делаем что-то
}

В этом случае Session должен реализовывать интерфейс IDisposable.

В Python класс, который мы используем, должен реализовывать методы __enter__ и __exit__. Это выглядит так:

# Python код
with Session() as session:
    # делаем что-то

Как уже отметили другие, вы всегда можете использовать оператор try/finally во всех языках для реализации аналогичного механизма. Это просто синтаксический сахар.

0

В Python при использовании оператора with вызов метода __enter__ происходит, когда происходит вход в контекст, и ресурсы необходимо захватить. Когда исполнение покидает этот контекст, Python вызывает метод __exit__, чтобы освободить ресурс.

Рассмотрим менеджеры контекста и оператор with в Python. Менеджер контекста — это простой «протокол» (или интерфейс), которому должен следовать ваш объект, чтобы его можно было использовать с оператором with. Основная вещь, которую вам нужно сделать, — это добавить методы enter и exit в объект, если вы хотите, чтобы он функционировал как менеджер контекста. Python будет вызывать эти два метода в соответствующие моменты цикла управления ресурсами.

Посмотрим, как это может выглядеть на практике. Вот пример простой реализации менеджера контекста для функции open():

class ManagedFile:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        self.file = open(self.name, 'w')
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()

Наш класс ManagedFile следует протоколу менеджера контекста и теперь поддерживает оператор with.

>>> with ManagedFile('hello.txt') as f:
...    f.write('hello, world!')
...    f.write('bye now')

Python вызывает метод enter, когда исполнение входит в контекст оператора with, и пора захватить ресурс. Когда исполнение покидает контекст, Python вызывает метод exit, чтобы освободить ресурс.

Однако написание менеджера контекста на основе класса — не единственный способ поддержать оператор with в Python. Утилитный модуль contextlib в стандартной библиотеке предоставляет несколько дополнительных абстракций, основанных на базовом протоколе менеджера контекста. Это может облегчить вам жизнь, если ваши случаи использования совпадают с тем, что предлагает contextlib.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь