Асинхронные контекстные менеджеры в Python (введенные в Python 3.7 с помощью библиотеки asyncio) позволяют безопасно и удобно управлять асинхронными ресурсами, такими как асинхронные соединения, асинхронные файловые операции или любые другие объекты, требующие асинхронной инициализации и завершения.
Основной механизм заключается в использовании двух специальных методов:
__aenter__(self): Этот метод вызывается при входе в блок async with. Он должен быть корутиной (т.е., определен с помощью async def). Он выполняет асинхронные операции настройки, такие как установление соединения или открытие файла. Он может возвращать значение, которое будет присвоено переменной, указанной после as в операторе async with. Если он вызывает исключение, блок async with не будет выполнен.__aexit__(self, exc_type, exc_val, exc_tb): Этот метод вызывается при выходе из блока async with, независимо от того, был ли он завершен нормально или с исключением. Он также должен быть корутиной. Он выполняет асинхронные операции завершения, такие как закрытие соединения или закрытие файла. Принимает три аргумента:exc_type: Тип исключения, если таковое возникло, иначе None.exc_val: Экземпляр исключения, если таковое возникло, иначе None.exc_tb: Трассировка стека исключения, если таковое возникло, иначе None.Если __aexit__ возвращает True, исключение, если оно было, подавляется (не распространяется дальше). Если возвращается None или любое другое значение, исключение распространяется как обычно.
Пример:
import asyncio
class AsyncResource:
async def open(self):
print("Opening resource...")
await asyncio.sleep(0.1) # Simulate async operation
print("Resource opened.")
async def close(self):
print("Closing resource...")
await asyncio.sleep(0.1) # Simulate async operation
print("Resource closed.")
class AsyncContextManager:
def __init__(self, resource):
self.resource = resource
async def __aenter__(self):
print("Entering context...")
await self.resource.open()
return self.resource # Возвращаем ресурс, который будет доступен внутри блока async with
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Exiting context...")
await self.resource.close()
print("Context exited.")
async def main():
resource = AsyncResource()
async with AsyncContextManager(resource) as res:
print("Inside the async with block...")
# Используем ресурс res (который равен resource)
await asyncio.sleep(0.2)
print("Doing something with the resource...")
print("After the async with block.")
if __name__ == "__main__":
asyncio.run(main())
Ключевые моменты:
__aenter__ и __aexit__ должны быть асинхронными.__aexit__ обеспечивает надежную обработку исключений, гарантируя освобождение ресурсов даже в случае ошибок.async with, __aexit__ всегда будет вызван для очистки.