Менеджеры контекста (оператор «with»)

Введение в контекстные менеджеры и оператор with

Менеджер контекста является объектом , который получает уведомление , когда контекст (блок кода) начинается и заканчивается. Вы обычно используете один с with заявлением. Он заботится об уведомлении.

Например, файловые объекты являются контекстными менеджерами. Когда контекст заканчивается, объект файла закрывается автоматически:

 open_file = open(filename)
with open_file:
    file_contents = open_file.read()

# the open_file object has automatically been closed.

 

В приведенном выше примере, как правило , упрощена, используя в as ключевого слова:

 with open(filename) as open_file:
    file_contents = open_file.read()

# the open_file object has automatically been closed.

 

Все, что завершает выполнение блока, вызывает метод exit менеджера контекста. Это включает исключения и может быть полезно, когда ошибка приводит к преждевременному выходу из открытого файла или соединения. Выход из сценария без надлежащего закрытия файлов / соединений - плохая идея, которая может привести к потере данных или другим проблемам. С помощью диспетчера контекста вы можете всегда принимать меры предосторожности, чтобы предотвратить повреждение или потерю таким образом. Эта функция была добавлена ​​в Python 2.5.

Присвоение цели

Многие контекстные менеджеры возвращают объект при вводе. Вы можете назначить этот объект на новое имя в with заявлением.

Например, с помощью подключения к базе данных в with утверждением может дать вам объект курсора:

 with database_connection as cursor:
    cursor.execute(sql_query)

 

Файловые объекты возвращаются сами по себе, это позволяет как открыть объект файла, так и использовать его в качестве диспетчера контекста в одном выражении:

 with open(filename) as open_file:
    file_contents = open_file.read() 

Написание собственного менеджера контекста

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

 class AContextManager():

    def __enter__(self):
        print("Entered")
        # optionally return an object
        return "A-instance"

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exited" + (" (with an exception)" if exc_type else ""))
        # return True if you want to suppress the exception

 

Если контекст выходит с исключением, информация о том , что исключение будет передан как тройной exc_type , exc_value , traceback (это те же переменные , как возвращаемое sys.exc_info() функции). Если контекст выходит нормально, все три из этих аргументов не будет None .

Если исключение происходит , и передается в __exit__ метод, метод может возвращать True , чтобы подавить исключение, или исключение будет вновь поднят в конце __exit__ функции.

 with AContextManager() as a:
    print("a is %r" % a)
# Entered
# a is 'A-instance'
# Exited

with AContextManager() as a:
    print("a is %d" % a)
# Entered
# Exited (with an exception)
# Traceback (most recent call last):
#   File "<stdin>", line 2, in <module>
# TypeError: %d format: a number is required, not str

 

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

Если вам необходим только __exit__ метод, вы можете вернуть экземпляр менеджера контекста:

 class MyContextManager:
    def __enter__(self):
        return self

    def __exit__(self):
        print('something') 

Написание собственного менеджера контекста с использованием синтаксиса генератора

Также можно написать менеджер контекста с использованием синтаксиса генератора благодаря contextlib.contextmanager декоратора:

 import contextlib

@contextlib.contextmanager
def context_manager(num):
    print('Enter')
    yield num + 1
    print('Exit')

with context_manager(2) as cm:
    # the following instructions are run when the 'yield' point of the context
    # manager is reached.
    # 'cm' will have the value that was yielded
    print('Right in the middle with cm = {}'.format(cm))

 

производит:

 Enter
Right in the middle with cm = 3
Exit

 

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

Если исключение должно быть обработано менеджером контекста, A try..except..finally -блок может быть записана в генераторе и каких - либо исключений , затронутые в with -блоком будут обработаны этим блоком исключений.

 @contextlib.contextmanager
def error_handling_context_manager(num):
    print("Enter")
    try:
        yield num + 1
    except ZeroDivisionError:
        print("Caught error")
    finally:
        print("Cleaning up")
    print("Exit")

with error_handling_context_manager(-1) as cm:
    print("Dividing by cm = {}".format(cm))
    print(2 / cm)

 

Это производит:

 Enter
Dividing by cm = 0
Caught error
Cleaning up
Exit 

Несколько контекстных менеджеров

Вы можете открыть несколько менеджеров контента одновременно:

 with open(input_path) as input_file, open(output_path, 'w') as output_file:

    # do something with both files. 

    # e.g. copy the contents of input_file into output_file
    for line in input_file:
        output_file.write(line + '\n')


 

Это имеет тот же эффект, что и вложенные контекстные менеджеры:

 with open(input_path) as input_file:
    with open(output_path, 'w') as output_file:
        for line in input_file:
            output_file.write(line + '\n') 

Управление ресурсами

 class File():
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.open_file = open(self.filename, self.mode)
        return self.open_file

    def __exit__(self, *args):
        self.open_file.close()
 

__init__() метод устанавливает объект, в данном случае настраивает имя файла и режим открытия файла. __enter__() открывает и возвращает файл и __exit__() просто закрывает его.

Используя эти магические методы ( __enter__ , __exit__ ) позволяет реализовать объекты , которые могут быть легко использованы with с утверждением.

Используйте класс File:

 for _ in range(10000):
    with File('foo.txt', 'w') as f:
        f.write('foo')