contextlib
— Утиліти для контекстів операторів with
¶
Вихідний код: Lib/contextlib.py
Цей модуль надає утиліти для типових завдань, пов’язаних із оператором with
. Для отримання додаткової інформації див. також Типи менеджера контексту і З менеджерами контексту операторів.
Комунальні послуги¶
Надані функції та класи:
- class contextlib.AbstractContextManager¶
abstract base class для класів, які реалізують
object.__enter__()
іobject.__exit__()
. Надається реалізація за замовчуванням дляobject.__enter__()
, яка повертаєself
, тоді якobject.__exit__()
є абстрактним методом, який за замовчуванням повертаєNone
. Дивіться також визначення Типи менеджера контексту.Added in version 3.6.
- class contextlib.AbstractAsyncContextManager¶
abstract base class для класів, які реалізують
object.__aenter__()
іobject.__aexit__()
. Надається реалізація за замовчуванням дляobject.__aenter__()
, яка повертаєself
, тоді якobject.__aexit__()
є абстрактним методом, який за замовчуванням повертаєNone
. Дивіться також визначення Менеджери асинхронного контексту.Added in version 3.7.
- @contextlib.contextmanager¶
This function is a decorator that can be used to define a factory function for
with
statement context managers, without needing to create a class or separate__enter__()
and__exit__()
methods.Хоча багато об’єктів нативно підтримують використання операторів in with, інколи необхідно керувати ресурсом, який сам по собі не є менеджером контексту та не реалізує метод
close()
для використання зcontextlib. закриття
Абстрактним прикладом може бути наступне, щоб забезпечити правильне керування ресурсами:
from contextlib import contextmanager @contextmanager def managed_resource(*args, **kwds): # Code to acquire resource, e.g.: resource = acquire_resource(*args, **kwds) try: yield resource finally: # Code to release resource, e.g.: release_resource(resource)
The function can then be used like this:
>>> with managed_resource(timeout=3600) as resource: ... # Resource is released at the end of this block, ... # even if code in the block raises an exception
Функція, яка декорується, має повертати generator-ітератор під час виклику. Цей ітератор має давати рівно одне значення, яке буде прив’язано до цілей у пропозиції
as
інструкціїwith
, якщо така є.У точці, де генератор поступається, виконується блок, вкладений у оператор
with
. Потім генератор відновлюється після виходу з блоку. Якщо в блоці виникає необроблена виняткова ситуація, вона повторно створюється всередині генератора в точці, де відбувся вихід. Таким чином, ви можете використовувати операторtry
…except
…finally
, щоб перехопити помилку (якщо така є) або забезпечити виконання певного очищення. Якщо виняток перехоплюється лише для того, щоб зареєструвати його або виконати певну дію (а не повністю його придушити), генератор повинен повторно викликати цей виняток. В іншому випадку менеджер контексту генератора вкаже операторуwith
, що виняток було оброблено, і виконання буде відновлено оператором, що слідує безпосередньо за операторомwith
.contextmanager()
використовуєContextDecorator
, тому створені ним менеджери контексту можна використовувати як декоратори, а також у операторахwith
. Коли використовується як декоратор, новий екземпляр генератора неявно створюється під час кожного виклику функції (це дозволяє «одноразовим» контекстним менеджерам, створенимcontextmanager()
, відповідати вимогам, щоб контекстні менеджери підтримували кілька викликів, щоб використовувати як декоратори).Змінено в версії 3.2: Використання
ContextDecorator
.
- @contextlib.asynccontextmanager¶
Подібно до
contextmanager()
, але створює асинхронний менеджер контексту.This function is a decorator that can be used to define a factory function for
async with
statement asynchronous context managers, without needing to create a class or separate__aenter__()
and__aexit__()
methods. It must be applied to an asynchronous generator function.Простий приклад:
from contextlib import asynccontextmanager @asynccontextmanager async def get_connection(): conn = await acquire_db_connection() try: yield conn finally: await release_db_connection(conn) async def get_all_users(): async with get_connection() as conn: return conn.query('SELECT ...')
Added in version 3.7.
Менеджери контексту, визначені за допомогою
asynccontextmanager()
, можна використовувати як декоратори або за допомогою операторівasync with
:import time from contextlib import asynccontextmanager @asynccontextmanager async def timeit(): now = time.monotonic() try: yield finally: print(f'it took {time.monotonic() - now}s to run') @timeit() async def main(): # ... async code ...
При використанні як декоратора новий екземпляр генератора неявно створюється під час кожного виклику функції. Це дозволяє «одноразовим» контекстним менеджерам, створеним
asynccontextmanager()
, відповідати вимогам, щоб контекстні менеджери підтримували кілька викликів, щоб використовувати їх як декоратори.Змінено в версії 3.10: Менеджери асинхронного контексту, створені за допомогою
asynccontextmanager()
, можна використовувати як декоратори.
- contextlib.closing(thing)¶
Повертає контекстний менеджер, який закриває річ після завершення блоку. Це в основному еквівалентно:
from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close()
І дозволяє писати такий код:
from contextlib import closing from urllib.request import urlopen with closing(urlopen('https://www.python.org')) as page: for line in page: print(line)
без необхідності явного закриття
сторінки
. Навіть якщо станеться помилка,page.close()
буде викликано під час виходу з блокуwith
.Примітка
Most types managing resources support the context manager protocol, which closes thing on leaving the
with
statement. As such,closing()
is most useful for third party types that don’t support context managers. This example is purely for illustration purposes, asurlopen()
would normally be used in a context manager.
- contextlib.aclosing(thing)¶
Повертає менеджер асинхронного контексту, який викликає метод
aclose()
thing після завершення блоку. Це в основному еквівалентно:from contextlib import asynccontextmanager @asynccontextmanager async def aclosing(thing): try: yield thing finally: await thing.aclose()
Важливо, що
aclosing()
підтримує детерміноване очищення асинхронних генераторів, коли вони виходять раніше черезbreak
або виняток. Наприклад:from contextlib import aclosing async with aclosing(my_generator()) as values: async for value in values: if value == 42: break
Цей шаблон гарантує, що асинхронний код виходу генератора виконується в тому самому контексті, що й його ітерації (щоб винятки та контекстні змінні працювали належним чином, а код виходу не запускався після закінчення часу існування певного завдання, від якого він залежить).
Added in version 3.10.
- contextlib.nullcontext(enter_result=None)¶
Повертає контекстний менеджер, який повертає enter_result із
__enter__
, але в іншому випадку нічого не робить. Він призначений для використання як резервний для додаткового контекстного менеджера, наприклад:def myfunction(arg, ignore_exceptions=False): if ignore_exceptions: # Use suppress to ignore all exceptions. cm = contextlib.suppress(Exception) else: # Do not ignore any exceptions, cm has no effect. cm = contextlib.nullcontext() with cm: # Do something
Приклад використання enter_result:
def process_file(file_or_path): if isinstance(file_or_path, str): # If string, open file cm = open(file_or_path) else: # Caller is responsible for closing file cm = nullcontext(file_or_path) with cm as file: # Perform processing on the file
Його також можна використовувати як заміну для асинхронних контекстних менеджерів:
async def send_http(session=None): if not session: # If no http session, create it with aiohttp cm = aiohttp.ClientSession() else: # Caller is responsible for closing the session cm = nullcontext(session) async with cm as session: # Send http requests with session
Added in version 3.7.
Змінено в версії 3.10: Додано підтримку asynchronous context manager.
- contextlib.suppress(*exceptions)¶
Повертає диспетчер контексту, який пригнічує будь-які з указаних винятків, якщо вони трапляються в тілі оператора
with
, а потім відновлює виконання з першим оператором, що йде після кінця оператораwith
.Як і будь-який інший механізм, який повністю пригнічує винятки, цей менеджер контексту слід використовувати лише для покриття дуже конкретних помилок, коли, як відомо, правильно продовжувати виконання програми.
Наприклад:
from contextlib import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') with suppress(FileNotFoundError): os.remove('someotherfile.tmp')
Цей код еквівалентний:
try: os.remove('somefile.tmp') except FileNotFoundError: pass try: os.remove('someotherfile.tmp') except FileNotFoundError: pass
Цей контекстний менеджер є reentrant.
If the code within the
with
block raises aBaseExceptionGroup
, suppressed exceptions are removed from the group. Any exceptions of the group which are not suppressed are re-raised in a new group which is created using the original group’sderive()
method.Added in version 3.4.
Змінено в версії 3.12:
suppress
now supports suppressing exceptions raised as part of aBaseExceptionGroup
.
- contextlib.redirect_stdout(new_target)¶
Менеджер контексту для тимчасового переспрямування
sys.stdout
на інший файл або файлоподібний об’єкт.Цей інструмент додає гнучкості існуючим функціям або класам, вихід яких підключено до стандартного виводу.
Наприклад, вихід
help()
зазвичай надсилається до sys.stdout. Ви можете записати цей вивід у рядок, перенаправивши вивід на об’єктio.StringIO
. Потік заміни повертається з методу__enter__
і тому доступний як ціль оператораwith
:with redirect_stdout(io.StringIO()) as f: help(pow) s = f.getvalue()
Щоб надіслати вивід
help()
у файл на диску, перенаправте вивід у звичайний файл:with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)
Щоб надіслати результат
help()
до sys.stderr:with redirect_stdout(sys.stderr): help(pow)
Зауважте, що глобальний побічний ефект
sys.stdout
означає, що цей контекстний менеджер не підходить для використання в бібліотечному коді та більшості потокових програм. Це також не впливає на вихідні дані підпроцесів. Однак це все ще корисний підхід для багатьох службових сценаріїв.Цей контекстний менеджер є reentrant.
Added in version 3.4.
- contextlib.redirect_stderr(new_target)¶
Подібно до
redirect_stdout()
, але перенаправляєsys.stderr
до іншого файлу або файлоподібного об’єкта.Цей контекстний менеджер є reentrant.
Added in version 3.5.
- contextlib.chdir(path)¶
Non parallel-safe context manager to change the current working directory. As this changes a global state, the working directory, it is not suitable for use in most threaded or async contexts. It is also not suitable for most non-linear code execution, like generators, where the program execution is temporarily relinquished – unless explicitly desired, you should not yield when this context manager is active.
This is a simple wrapper around
chdir()
, it changes the current working directory upon entering and restores the old one on exit.Цей контекстний менеджер є reentrant.
Added in version 3.11.
- class contextlib.ContextDecorator¶
Базовий клас, який дозволяє менеджеру контексту також використовуватися як декоратор.
Менеджери контексту, успадковані від
ContextDecorator
, мають реалізувати__enter__
і__exit__
як зазвичай.__exit__
зберігає необов’язкову обробку винятків, навіть якщо використовується як декоратор.ContextDecorator
використовуєтьсяcontextmanager()
, тому ви отримуєте цю функціональність автоматично.Приклад
ContextDecorator
:from contextlib import ContextDecorator class mycontext(ContextDecorator): def __enter__(self): print('Starting') return self def __exit__(self, *exc): print('Finishing') return False
The class can then be used like this:
>>> @mycontext() ... def function(): ... print('The bit in the middle') ... >>> function() Starting The bit in the middle Finishing >>> with mycontext(): ... print('The bit in the middle') ... Starting The bit in the middle Finishing
Ця зміна є просто синтаксичним цукром для будь-якої конструкції наступної форми:
def f(): with cm(): # Do stuff
ContextDecorator
дозволяє замість цього писати:@cm() def f(): # Do stuff
Це дає зрозуміти, що
cm
застосовується до всієї функції, а не лише до її частини (і зберегти рівень відступу теж добре).Існуючі контекстні менеджери, які вже мають базовий клас, можна розширити за допомогою
ContextDecorator
як класу mixin:from contextlib import ContextDecorator class mycontext(ContextBaseClass, ContextDecorator): def __enter__(self): return self def __exit__(self, *exc): return False
Примітка
Оскільки декорована функція повинна мати можливість викликатися кілька разів, базовий менеджер контексту повинен підтримувати використання в кількох операторах
with
. Якщо це не так, тоді слід використовувати оригінальну конструкцію з явним операторомwith
усередині функції.Added in version 3.2.
- class contextlib.AsyncContextDecorator¶
Подібно до
ContextDecorator
, але лише для асинхронних функцій.Приклад
AsyncContextDecorator
:from asyncio import run from contextlib import AsyncContextDecorator class mycontext(AsyncContextDecorator): async def __aenter__(self): print('Starting') return self async def __aexit__(self, *exc): print('Finishing') return False
The class can then be used like this:
>>> @mycontext() ... async def function(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle Finishing >>> async def function(): ... async with mycontext(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle Finishing
Added in version 3.10.
- class contextlib.ExitStack¶
Менеджер контексту, розроблений для полегшення програмного поєднання інших менеджерів контексту та функцій очищення, особливо тих, які є необов’язковими або іншим чином керуються вхідними даними.
Наприклад, набір файлів можна легко обробити в одному операторі with наступним чином:
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # All opened files will automatically be closed at the end of # the with statement, even if attempts to open files later # in the list raise an exception
The
__enter__()
method returns theExitStack
instance, and performs no additional operations.Кожен екземпляр підтримує стек зареєстрованих зворотних викликів, які викликаються у зворотному порядку, коли екземпляр закривається (явно чи неявно в кінці оператора
with
). Зауважте, що зворотні виклики не викликаються неявно, коли примірник стеку контексту збирається сміттям.Ця модель стеку використовується для того, щоб контекстні менеджери, які отримують свої ресурси у своєму методі
__init__
(наприклад, файлові об’єкти), могли оброблятися правильно.Оскільки зареєстровані зворотні виклики викликаються в порядку, зворотному реєстрації, це в кінцевому підсумку поводиться так, ніби декілька вкладених операторів
with
використовувалися із зареєстрованим набором зворотних викликів. Це навіть поширюється на обробку винятків - якщо внутрішній зворотний виклик пригнічує або замінює виняток, то зовнішнім зворотним викликам будуть передані аргументи на основі цього оновленого стану.Це API відносно низького рівня, який піклується про деталі правильного розгортання стека зворотних викликів виходу. Він забезпечує відповідну основу для контекстних менеджерів вищого рівня, які маніпулюють стеком виходу у специфічних для програми способах.
Added in version 3.3.
- enter_context(cm)¶
Enters a new context manager and adds its
__exit__()
method to the callback stack. The return value is the result of the context manager’s own__enter__()
method.Ці контекстні менеджери можуть придушувати винятки так само, як вони зазвичай робили б, якщо використовувати безпосередньо як частину оператора
with
.Змінено в версії 3.11: Raises
TypeError
instead ofAttributeError
if cm is not a context manager.
- push(exit)¶
Adds a context manager’s
__exit__()
method to the callback stack.As
__enter__
is not invoked, this method can be used to cover part of an__enter__()
implementation with a context manager’s own__exit__()
method.If passed an object that is not a context manager, this method assumes it is a callback with the same signature as a context manager’s
__exit__()
method and adds it directly to the callback stack.By returning true values, these callbacks can suppress exceptions the same way context manager
__exit__()
methods can.Переданий об’єкт повертається функцією, що дозволяє використовувати цей метод як декоратор функції.
- callback(callback, /, *args, **kwds)¶
Приймає довільну функцію зворотного виклику та аргументи та додає їх до стеку зворотного виклику.
На відміну від інших методів, зворотні виклики, додані таким чином, не можуть придушувати винятки (оскільки їм ніколи не передаються деталі винятку).
Переданий зворотний виклик повертається функцією, що дозволяє використовувати цей метод як декоратор функції.
- pop_all()¶
Передає стек зворотних викликів до нового екземпляра
ExitStack
і повертає його. Жодні зворотні виклики не викликаються цією операцією - замість цього вони тепер будуть викликані, коли новий стек закривається (явно чи неявно в кінці оператораwith
).Наприклад, групу файлів можна відкрити за допомогою операції «все або нічого» наступним чином:
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # Hold onto the close method, but don't call it yet. close_files = stack.pop_all().close # If opening any file fails, all previously opened files will be # closed automatically. If all files are opened successfully, # they will remain open even after the with statement ends. # close_files() can then be invoked explicitly to close them all.
- close()¶
Негайно розгортає стек зворотних викликів, викликаючи зворотні виклики в порядку, зворотному реєстрації. Для будь-яких менеджерів контексту та зареєстрованих зворотних викликів виходу передані аргументи вказуватимуть, що винятків не сталося.
- class contextlib.AsyncExitStack¶
асинхронний контекстний менеджер, подібний до
ExitStack
, який підтримує поєднання синхронних і асинхронних контекстних менеджерів, а також має співпрограми для логіки очищення.The
close()
method is not implemented;aclose()
must be used instead.- coroutine enter_async_context(cm)¶
Similar to
ExitStack.enter_context()
but expects an asynchronous context manager.Змінено в версії 3.11: Raises
TypeError
instead ofAttributeError
if cm is not an asynchronous context manager.
- push_async_exit(exit)¶
Similar to
ExitStack.push()
but expects either an asynchronous context manager or a coroutine function.
- push_async_callback(callback, /, *args, **kwds)¶
Similar to
ExitStack.callback()
but expects a coroutine function.
- coroutine aclose()¶
Similar to
ExitStack.close()
but properly handles awaitables.
Продовжуємо приклад для
asynccontextmanager()
:async with AsyncExitStack() as stack: connections = [await stack.enter_async_context(get_connection()) for i in range(5)] # All opened connections will automatically be released at the end of # the async with statement, even if attempts to open a connection # later in the list raise an exception.
Added in version 3.7.
Приклади та рецепти¶
У цьому розділі описано деякі приклади та рецепти ефективного використання інструментів, наданих contextlib
.
Підтримка змінної кількості контекстних менеджерів¶
Основний варіант використання ExitStack
— це той, який наведено в документації класу: підтримка змінної кількості контекстних менеджерів та інших операцій очищення в одному операторі with
. Варіативність може виникати через кількість необхідних менеджерів контексту, які керуються введенням користувача (наприклад, відкриття вказаної користувачем колекції файлів), або через те, що деякі менеджери контексту є необов’язковими:
with ExitStack() as stack:
for resource in resources:
stack.enter_context(resource)
if need_special_resource():
special = acquire_special_resource()
stack.callback(release_special_resource, special)
# Perform operations that use the acquired resources
Як показано, ExitStack
також дозволяє досить легко використовувати оператори with
для керування довільними ресурсами, які спочатку не підтримують протокол керування контекстом.
Перехоплення винятків із методів __enter__
¶
Час від часу бажано перехоплювати винятки з реалізації методу __enter__
, без випадкового перехоплення винятків з тіла оператора with
або методу __exit__
контекстного менеджера. За допомогою ExitStack
кроки в протоколі керування контекстом можна трохи розділити, щоб дозволити це:
stack = ExitStack()
try:
x = stack.enter_context(cm)
except Exception:
# handle __enter__ exception
else:
with stack:
# Handle normal case
Насправді необхідність зробити це означає, що основний API має надавати інтерфейс прямого керування ресурсами для використання з операторами try
/except
/finally
, але не з усіма API добре розроблені в цьому плані. Коли контекстний менеджер є єдиним наданим API керування ресурсами, тоді ExitStack
може полегшити обробку різноманітних ситуацій, які не можна обробити безпосередньо в операторі with
.
Очищення в реалізації __enter__
¶
As noted in the documentation of ExitStack.push()
, this
method can be useful in cleaning up an already allocated resource if later
steps in the __enter__()
implementation fail.
Ось приклад виконання цього для контекстного менеджера, який приймає функції отримання та звільнення ресурсів разом із додатковою функцією перевірки та відображає їх у протоколі керування контекстом:
from contextlib import contextmanager, AbstractContextManager, ExitStack
class ResourceManager(AbstractContextManager):
def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
self.acquire_resource = acquire_resource
self.release_resource = release_resource
if check_resource_ok is None:
def check_resource_ok(resource):
return True
self.check_resource_ok = check_resource_ok
@contextmanager
def _cleanup_on_error(self):
with ExitStack() as stack:
stack.push(self)
yield
# The validation check passed and didn't raise an exception
# Accordingly, we want to keep the resource, and pass it
# back to our caller
stack.pop_all()
def __enter__(self):
resource = self.acquire_resource()
with self._cleanup_on_error():
if not self.check_resource_ok(resource):
msg = "Failed validation for {!r}"
raise RuntimeError(msg.format(resource))
return resource
def __exit__(self, *exc_details):
# We don't need to duplicate any of our resource release logic
self.release_resource()
Заміна будь-якого використання змінних try-finally
і прапорців¶
Зразок, який ви іноді побачите, — це інструкція try-finally
зі змінною-прапором, яка вказує, чи має бути виконано тіло пропозиції finally
. У своїй найпростішій формі (з якою вже не можна впоратися лише за допомогою пропозиції except
), це виглядає приблизно так:
cleanup_needed = True
try:
result = perform_operation()
if result:
cleanup_needed = False
finally:
if cleanup_needed:
cleanup_resources()
Як і у випадку з будь-яким кодом, заснованим на інструкціях try
, це може спричинити проблеми з розробкою та переглядом, оскільки код налаштування та код очищення можуть бути розділені довільно довгими частинами коду.
ExitStack
дає змогу замість цього зареєструвати зворотний виклик для виконання в кінці оператора with
, а потім вирішити пропустити виконання цього зворотного виклику:
from contextlib import ExitStack
with ExitStack() as stack:
stack.callback(cleanup_resources)
result = perform_operation()
if result:
stack.pop_all()
This allows the intended cleanup behaviour to be made explicit up front, rather than requiring a separate flag variable.
Якщо певна програма часто використовує цей шаблон, його можна ще більше спростити за допомогою невеликого допоміжного класу:
from contextlib import ExitStack
class Callback(ExitStack):
def __init__(self, callback, /, *args, **kwds):
super().__init__()
self.callback(callback, *args, **kwds)
def cancel(self):
self.pop_all()
with Callback(cleanup_resources) as cb:
result = perform_operation()
if result:
cb.cancel()
Якщо очищення ресурсу ще не акуратно об’єднано в окрему функцію, то все ще можна використовувати форму декоратора ExitStack.callback()
, щоб оголосити очищення ресурсу заздалегідь:
from contextlib import ExitStack
with ExitStack() as stack:
@stack.callback
def cleanup_resources():
...
result = perform_operation()
if result:
stack.pop_all()
Через те, як працює протокол декоратора, функція зворотного виклику, оголошена таким чином, не може приймати жодних параметрів. Натомість доступ до будь-яких ресурсів, які потрібно звільнити, має здійснюватися як змінні закриття.
Використання контекстного менеджера як декоратора функції¶
ContextDecorator
дає змогу використовувати менеджер контексту як у звичайному операторі with
, так і як декоратор функції.
Наприклад, іноді корисно обернути функції або групи операторів за допомогою реєстратора, який може відстежувати час входу та час виходу. Замість написання як декоратора функції, так і менеджера контексту для завдання, успадкування від ContextDecorator
надає обидві можливості в одному визначенні:
from contextlib import ContextDecorator
import logging
logging.basicConfig(level=logging.INFO)
class track_entry_and_exit(ContextDecorator):
def __init__(self, name):
self.name = name
def __enter__(self):
logging.info('Entering: %s', self.name)
def __exit__(self, exc_type, exc, exc_tb):
logging.info('Exiting: %s', self.name)
Екземпляри цього класу можна використовувати і як менеджер контексту:
with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()
А також як декоратор функцій:
@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()
Note that there is one additional limitation when using context managers
as function decorators: there’s no way to access the return value of
__enter__()
. If that value is needed, then it is still necessary to use
an explicit with
statement.
Менеджери контексту для одноразового, багаторазового та повторного входу¶
Більшість контекстних менеджерів написані таким чином, що вони можуть бути ефективно використані в операторі with
лише один раз. Ці одноразові контекстні менеджери потрібно створювати заново кожного разу, коли вони використовуються — спроба використати їх вдруге призведе до виключення або іншим чином не працюватиме належним чином.
Це загальне обмеження означає, що загалом доцільно створювати менеджери контексту безпосередньо в заголовку оператора with
, де вони використовуються (як показано в усіх наведених вище прикладах використання).
Файли є прикладом ефективних одноразових контекстних менеджерів, оскільки перший оператор with
закриває файл, запобігаючи будь-яким подальшим операціям вводу-виводу з використанням цього файлового об’єкта.
Менеджери контексту, створені за допомогою contextmanager()
, також є одноразовими менеджерами контексту, і вони скаржаться на те, що базовий генератор не працює, якщо буде зроблена спроба використати їх вдруге:
>>> from contextlib import contextmanager
>>> @contextmanager
... def singleuse():
... print("Before")
... yield
... print("After")
...
>>> cm = singleuse()
>>> with cm:
... pass
...
Before
After
>>> with cm:
... pass
...
Traceback (most recent call last):
...
RuntimeError: generator didn't yield
Реентерабельні контекстні менеджери¶
Більш складні контекстні менеджери можуть бути «реентерабельними». Ці менеджери контексту можна використовувати не лише в кількох операторах with
, але також всередині оператора with
, який уже використовує той самий менеджер контексту.
threading.RLock
is an example of a reentrant context manager, as are
suppress()
, redirect_stdout()
, and chdir()
. Here’s a very
simple example of reentrant use:
>>> from contextlib import redirect_stdout
>>> from io import StringIO
>>> stream = StringIO()
>>> write_to_stream = redirect_stdout(stream)
>>> with write_to_stream:
... print("This is written to the stream rather than stdout")
... with write_to_stream:
... print("This is also written to the stream")
...
>>> print("This is written directly to stdout")
This is written directly to stdout
>>> print(stream.getvalue())
This is written to the stream rather than stdout
This is also written to the stream
Приклади повторного входу в реальному світі, швидше за все, включають кілька функцій, які викликають одна одну, і, отже, вони набагато складніші, ніж цей приклад.
Зауважте також, що реентерабельність — це не те саме, що бути потокобезпечною. redirect_stdout()
, наприклад, точно небезпечний для потоків, оскільки він робить глобальну модифікацію стану системи шляхом прив’язки sys.stdout
до іншого потоку.
Багаторазові контекстні менеджери¶
Від одноразових і повторних контекстних менеджерів відрізняються «повторно використовувані» контекстні менеджери (або, якщо бути повністю явними, «повторно використовувані, але не повторні» контекстні менеджери, оскільки повторні контекстні менеджери також багаторазові). Ці контекстні менеджери підтримують багаторазове використання, але не працюватимуть (або не працюватимуть належним чином), якщо конкретний екземпляр контекстного менеджера вже використовувався в операторі containing with.
threading.Lock
є прикладом повторно використовуваного, але не реентрантного, контекстного менеджера (для повторного вхідного блокування замість цього необхідно використовувати threading.RLock
).
Іншим прикладом повторно використовуваного, але не реентрантного контекстного менеджера є ExitStack
, оскільки він викликає всі зареєстровані наразі зворотні виклики, коли залишає будь-який оператор with, незалежно від того, де ці зворотні виклики було додано:
>>> from contextlib import ExitStack
>>> stack = ExitStack()
>>> with stack:
... stack.callback(print, "Callback: from first context")
... print("Leaving first context")
...
Leaving first context
Callback: from first context
>>> with stack:
... stack.callback(print, "Callback: from second context")
... print("Leaving second context")
...
Leaving second context
Callback: from second context
>>> with stack:
... stack.callback(print, "Callback: from outer context")
... with stack:
... stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Callback: from outer context
Leaving outer context
Як показує вихід із прикладу, повторне використання одного об’єкта стека в кількох інструкціях with працює правильно, але спроба їх вкладення спричинить очищення стека в кінці внутрішнього оператора with, що навряд чи буде бажаною поведінкою.
Використання окремих екземплярів ExitStack
замість повторного використання одного екземпляра дозволяє уникнути цієї проблеми:
>>> from contextlib import ExitStack
>>> with ExitStack() as outer_stack:
... outer_stack.callback(print, "Callback: from outer context")
... with ExitStack() as inner_stack:
... inner_stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Leaving outer context
Callback: from outer context