Python Language
Контекстные менеджеры ("with" Statement)
Поиск…
Вступление
Хотя контекстные менеджеры 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')