[Python] Asyncio_06

atesi·2022년 9월 26일
0

asyncio

목록 보기
6/7

Asynchronous Iterators

IteratorsGenerators는 파이썬에서는 일반적인 도구이다. Pythontips에 작동방식에 대한 좋은 설명이 있다.

추상적으로 iterable은 for 루프로 루프할 수 있는 데이터의 소스를 나타내고, 비동기 iterable은 async for 루프로 루프할 수 있는 데이터의 소스를 나타낸다.

async for grain in reader.get_grains():
    # Do something with each grain object
    ...

위의 코드에서 메소드 reader.get_grains는 비동기 iterable객체를 반환하고 일반적인 for 루프와 마찬가지로 루프 내의 로컬 변수 grain에 각각을 할당하고 요소를 하나씩 끌어온다. 차이점은 iterable에서 파생된 비동기 이터레이터에서 다음 요소를 추출하는데 사용되는 메소드가 비동기 코루틴 메소드이고 그것의 출력이 대기된다는 것이다.

async for 작성은 await문을 사용한 코드의 함축이다.

async for a in async_iterable:
    await do_a_thing(a)

# Is equivalent to

it = async_iterable.__aiter__()
while True:
    try:
        a = await anext(it)
    except StopAsyncIteration:
        break

    await do_a_thing(a)

이런 이유로 await, async with, async for루프는 비동기 코드가 허용되는 컨택스트에서만 사용할 수 있다.

자신만의 비동기 이터러블을 매직 메서드로 구현하면 된다.

def __aiter__(self):
    ...

비동기 이터레이터를 반환한다.(코루틴메서드__aiter__가 아니다). 그리고 자신만의 비동기식 반복자를 구현하는 것도 쉽다. 다음과 같은 매직 메서드를 구현하는 객체를 생성하기만 하면 된다.

def __aiter__(self):
    return self

async def __anext__(self):
    ...

__aiter__self를 반환해야 하고 __anext__는 대기할 때마다 반복자의 다음 항목을 반환하는 코루틴 메서드여야 한다.

Async Generators

동기 제너러이터는 비동기 이터레이터 정의하는데 간단한 메소드로 사용할 수 있다. 따라서 간단한 비동기 생성기 메서드 사용법으로는 비동기 코루틴 메서드와 마찬가지로 async def를 사용하여 정의되지만 메서드 본문에는 키워드 yield를 적어도 하나 이상 포함해야한다.

async def async_generator_method_example(param):
    ...
    ...

    yield something

    ....
    ...

    yield something_else

    ...
    ... # etc ...

비동기 제너레이터 메소드는 비동기 제너레이터 객체를 반환하는 동기 메소드이다. 그것은 코루틴 메소드가 아니며 그것의 반환값을 기다리면 예외가 발생한다.

async def coroutine_method():
    return 3

async def generator_method():
    yield 3

# This is correct
r = await coroutine_method()

# This will raise an exception!
r = await generator_method()

그러나 호출에서 반환된 비동기 제너레이터 객체는 비동기 iterator의 예이므로 async for루프에서 사용할 수 있습니다.

# This is fine, and will print 3
async for r in generator_method():
    print(r)

특히 제너러이터 객체 g가 처음으로 g.__anext__()가 최초의 yield문에 도달할때까지 실행될 제터레이터의 코드블럭안의 코드에서 awaited하고 yield에 전달될 값은 await에 의해 반환될 것이고 g.__anext__()가 기다릴 때마다 코드는 마지막으로 중단된 위치에서 다음 yield 문에 도달할 때까지 계속 실행되고 해당 문의 값이 반환된다. 만약 제너레이터 메소드의 코드블럭이 return문에 도달하거나 코드블럭의 끝에 도달하면 g.__anext__()의 await가 StopAsyncIteration를 발생시킨다. 위에서 본것처럼 async for 루프에 의해 잡히고 루프가 정상적으로 종료 된다.

Advanced Asynchronous Generators

비동기 제너레이터를 더 고급으로 사용할 수 있지만 그렇게 하려면 async for루프와 async iterator 인터페이스에서 허용하는 것 이상으로 해야 한다.

사실 yield 제너레이터 내부의 각 명령문은 값을 반환 하고 값을 취하도록 만들 수 있다. 따라서 다음 코드가 유효하다.

async def advanced_generator(y):
    for i in range(0, 10):
        x = await do_something(y)
        y = yield x

그리고 이것을 사용하려면 async for루프를 사용할 수 없으며 대신 더 명시적이어야 한다.

it = advanced_generator(first_y)
x = await anext(it)

while True:
    y = await do_something_else(x)
    try:
        x = await it.asend(y)
    except StopAsyncIteration:
        break

이 코드는 호출될 때마다 제너레이터와 호출객체 간에 값을 주고 받는다. 특히 초기값 first_yy.로 제너레이터를 생성하여 시작한 다음 __anext__를 한 번 기다린다. 그러면 do_something 대기를 포함하는 제너레이터의 시작이 실행되고 반환된 값 x가 생성된다. 이 값은 호출자에게 반환되고 호출자는 이를 x에 할당하여 루프를 시작한다. 루프의 각 반복은 iteration이 반환한 마지막 값과 do_something_else를 기다린 다음 결과를 제너레이터에 보내고 yield 문의 반환 값이 된다.

profile
Action!

0개의 댓글