PEP 492–Coroutines with Async and Await Syntax

Python에 비동기 프로그래밍을 위한 async와 await 구문을 도입하여 코루틴(coroutine)을 명시적으로 정의하고 사용하는 방법을 제안한다.
Python 3.5부터 도입되었다.
기존의 제너레이터 기반 코루틴과 구분되는 네이티브 코루틴을 정의한다.
PEP 492는 비동기 프로그래밍을 더 명확하고 Pythonic하게 만들어 준다.

PEP 492의 주요 내용

네이티브 코루틴 정의

  • async def 키워드를 사용하여 네이티브 코루틴을 정의한다.

  • 이는 함수가 코루틴임을 명확히 나타내며, 기존의 yieldyield from 대신 await를 사용한다.

    1
    2
    3
    4
    
    async def fetch_data():
        """데이터를 비동기적으로 가져오는 네이티브 코루틴"""
        await asyncio.sleep(1)  # 비동기 작업 대기
        return "data"
    

await 표현식

  • await 키워드는 코루틴에서 다른 코루틴이나 비동기 작업의 완료를 기다릴 때 사용된다.

  • 이는 yield from의 역할을 대체하며, 비동기 작업의 결과를 반환한다.

    1
    2
    3
    4
    
    async def main():
        """메인 코루틴"""
        result = await fetch_data()  # fetch_data가 완료될 때까지 대기
        print(result)
    

비동기 컨텍스트 관리자

  • async with 구문을 통해 비동기 컨텍스트 관리자를 사용할 수 있다.

  • 이는 비동기 리소스 관리에 유용하다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    class AsyncContextManager:
        """비동기 컨텍스트 관리자 예제"""
    
        async def __aenter__(self):
            print("Entering context")
            return self
    
        async def __aexit__(self, exc_type, exc, tb):
            print("Exiting context")
    
    async def use_context():
        async with AsyncContextManager() as manager:
            print("Inside context")
    

비동기 반복자

  • async for 구문을 통해 비동기 반복자를 사용할 수 있으며, 이는 비동기 데이터 스트림 처리에 적합하다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class AsyncIterator:
    """비동기 반복자 예제"""

    def __init__(self):
        self.count = 0

    async def __aiter__(self):
        return self

    async def __anext__(self):
        if self.count >= 3:
            raise StopAsyncIteration
        self.count += 1
        return self.count

async def iterate():
    async for number in AsyncIterator():
        print(number)

주요 이점

  1. 동시성 처리의 간소화
  2. 코드의 가독성 향상
  3. 비동기 코드의 안전성 증가
  4. 성능 향상 (I/O 바운드 작업의 경우)

사용 사례

  1. 웹 애플리케이션
  2. 네트워크 프로그래밍
  3. 데이터베이스 작업
  4. 파일 I/O 작업

주의사항

  1. CPU 바운드 작업에는 적합하지 않음 (이 경우 멀티프로세싱 사용 권장)
  2. 모든 비동기 함수는 await로 실행해야 함
  3. 일반 함수 안에서는 await를 사용할 수 없음

의미

PEP 492는 Python에서 비동기 프로그래밍을 보다 직관적이고 효율적으로 수행할 수 있게 해준다.
async와 await 구문은 코드의 가독성을 높이고, 개발자가 비동기 작업의 흐름을 쉽게 이해할 수 있도록 돕는다.
이를 통해 Python은 다른 언어들처럼 현대적인 비동기 프로그래밍 패러다임을 지원하게 됨.

사용 예시

기본적인 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import asyncio

# 1. 기본적인 비동기 함수 정의
async def hello_world():
    # async def: 이 함수가 비동기 함수임을 선언
    print("시작")
    # await: 1초 대기하는 비동기 작업을 기다림
    await asyncio.sleep(1)
    print("1초 후")
    return "완료"

# 2. 비동기 함수 실행을 위한 메인 함수
async def main():
    # await를 사용해 비동기 함수의 완료를 기다림
    result = await hello_world()
    print(result)

# 3. 비동기 함수 실행
# asyncio.run(): 비동기 함수를 실행하기 위한 진입점
asyncio.run(main())

실용적인 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import asyncio
import aiohttp  # 비동기 HTTP 클라이언트 라이브러리

# 4. 여러 웹 요청을 비동기적으로 처리하는 예제
async def fetch_url(session, url: str) -> str:
    # aiohttp를 사용한 비동기 HTTP 요청
    async with session.get(url) as response:
        # await를 사용해 응답을 기다림
        return await response.text()

async def fetch_multiple_urls():
    # 여러 URL을 동시에 처리
    urls = [
        'http://example.com',
        'http://example.org',
        'http://example.net'
    ]
    
    # aiohttp 세션 생성
    async with aiohttp.ClientSession() as session:
        # 모든 URL에 대한 요청을 동시에 시작
        # asyncio.gather: 여러 코루틴을 동시에 실행
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

# 5. 비동기 컨텍스트 매니저 예제
class AsyncResource:
    async def __aenter__(self):
        # 비동기 컨텍스트 매니저 진입
        print("리소스 획득 중...")
        await asyncio.sleep(1)
        return self

    async def __aexit__(self, exc_type, exc, tb):
        # 비동기 컨텍스트 매니저 종료
        print("리소스 정리 중...")
        await asyncio.sleep(1)

# 6. 비동기 이터레이터 예제
class AsyncCounter:
    def __init__(self, limit):
        self.limit = limit
        self.counter = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self.counter < self.limit:
            self.counter += 1
            await asyncio.sleep(0.1)
            return self.counter
        raise StopAsyncIteration

# 7. 실제 사용 예제
async def example_usage():
    # 비동기 컨텍스트 매니저 사용
    async with AsyncResource() as resource:
        print("리소스 사용 중")

    # 비동기 이터레이터 사용
    async for number in AsyncCounter(3):
        print(f"카운터: {number}")

    # 병렬 작업 처리
    tasks = [
        asyncio.create_task(asyncio.sleep(1)),
        asyncio.create_task(asyncio.sleep(2)),
        asyncio.create_task(asyncio.sleep(3))
    ]
    # 모든 태스크가 완료될 때까지 대기
    await asyncio.gather(*tasks)

참고 및 출처