Поиск…


Вступление

Хотя контекстные менеджеры Python широко используются, мало кто понимает цель их использования. Эти операторы, обычно используемые при чтении и записи файлов, помогают приложению сохранять память системы и улучшать управление ресурсами, гарантируя, что определенные ресурсы используются только для определенных процессов. В этом разделе объясняется и демонстрируется использование контекстных менеджеров Python.

Синтаксис

  • с «context_manager» (как «псевдоним») (, «context_manager» (как «псевдоним»)?) *:

замечания

Контекстные менеджеры определены в PEP 343 . Они предназначены для использования в качестве более сжатого механизма управления ресурсами, чем try ... finally конструирует. Формальное определение состоит в следующем.

В этом PEP менеджеры контекста предоставляют __enter__() и __exit__() , которые вызывают при входе в тело оператора exit и выходят из него.

Затем далее определяется оператор with следующим образом.

with EXPR as VAR:
    BLOCK

Перевод вышеуказанного заявления:

   mgr = (EXPR)
   exit = type(mgr).__exit__  # Not calling it yet
   value = type(mgr).__enter__(mgr)
   exc = True
   try:
       try:
           VAR = value  # Only if "as VAR" is present
           BLOCK
       except:
           # The exceptional case is handled here
           exc = False
           if not exit(mgr, *sys.exc_info()):
               raise
           # The exception is swallowed if exit() returns true
   finally:
       # The normal and non-local-goto cases are handled here
       if exc:
           exit(mgr, None, None, None)

Введение в контекстные менеджеры и оператор 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.

Все, что заканчивает выполнение блока, вызывает вызов метода диспетчера контекста. Это включает исключения и может быть полезно, когда ошибка приводит к преждевременному выходу из открытого файла или соединения. Выход из сценария без должного закрытия файлов / соединений - плохая идея, которая может привести к потере данных или другим проблемам. Используя диспетчер контекста, вы можете гарантировать, что меры предосторожности всегда принимаются, чтобы предотвратить повреждение или потерю таким образом. Эта функция была добавлена ​​в 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

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

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

@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')


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow