[Python] async with - 비동기 컨텍스트 관리자
개요
Python의 컨텍스트 관리자란 with문을 사용해 리소스를 관리하는 구조로, 리소스의 할당이나 삭제를 자동으로 처리할 수 있는 코드 블록을 생성해 예외가 발생하더라고 리소스의 획득과 해제를 보장한다.
다만, 비동기 프로그래밍 방식에서는 비동기 방식으로 리소스를 획득하고 해제해야 하므로 with 문만으로는 충분하지 않다. 때문에 비동기 컨텍스트 관리자라는 개념이 발생했다. 이번 글에선 비동기 컨텍스트 관리자를 구현하고 사용하는 방법을 알아본다.
비동기 컨텍스트 관리자
비동기 컨텍스트 관리자는 enter와 exit 메서드에서 실행을 일시 중지할 수 있는 컨텍스트 관리자다.
async with EXPRESSION as TARGET:
SUITE
위의 async with문은 다음과 같이 이해할 수 있다.
manager = (EXPRESSION)
aenter = type(manager).__aenter__
aexit = type(manager).__aexit__
value = await aenter(manager)
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not await aexit(manager, *sys.exc_info()):
raise
finally:
if not hit_except:
await aexit(manager, None, None, None)
동기 방식의 with문보다 복잡해보이지만 비동기로 동작한다는 것만 제외하면 진입 시 enter, 종료 시 exit 함수가 실행된다는 구조는 동일하다. 다만, 비동기 프로그래밍이므로 각 함수 이름은 비동기임을 나타낼 수 있도록 aenter, aexit으로 명명하며, enter와 axit은 코루틴이어야 한다.
구현
아래 코드는 2025.01.15-[Python] context manager - 리소스 관리의 동기식 컨텍스트 매니저를 비동기식 컨텍스트 매니저로 변경한 예시이다. 기존과 동일하게 Python 매직 메서드를 직접 구현하는 방법으로 비동기 컨텍스트 매니저를 구현할 수 있다.
class ListManager:
async def __aenter__(self):
self.list = []
return self.list
async def __aexit__(self, exc_type, exc_val, exc_tb):
print(self.list)
구현한 클래스를 사용하면 다음과 같다. 비동기 프로그래밍이므로 호출 역시 비동기 함수 내에서 이루어져야 한다.
async def main():
async with ListManager() as lm:
lm.append(1)
lm.append(2)
if __name__ == '__main__':
asyncio.run(main())
출력 자체는 동기 방식으로 구현한 것과 동일한 것을 확인할 수 있다.
참고 문서
https://docs.python.org/ko/3/reference/compound_stmts.html#async-with
https://docs.python.org/ko/3/reference/datamodel.html#object.__aenter__
https://ojt90902.tistory.com/1334
https://medium.com/@shashikantrbl123/async-with-in-python-deb0e62693cc