Асинхронные понимания Python — как они работают?

У меня возникли проблемы с пониманием использования асинхронных понятий, представленных в Python 3.6. В качестве отказа от ответственности у меня нет большого опыта работы с асинхронным кодом в Python в целом.

Пример, приведенный в документе что нового в Python 3.6:

result = [i async for i in aiter() if i % 2]

В PEP это расширено до:

result = []
async for i in aiter():
    if i % 2:
        result.append(i)

Думаю, я понимаю, что функция aiter() вызывается асинхронно, так что каждая итерация aiter может выполняться без обязательного возврата предыдущей (или это понимание неверно?).

В чем я не уверен, так это в том, как это переводится в понимание списка здесь. Помещаются ли результаты в список в том порядке, в котором они возвращаются? Или в окончательном списке есть эффективные «заполнители», чтобы каждый результат помещался в список в правильном порядке? Или я неправильно об этом думаю?

Кроме того, может ли кто-нибудь предоставить реальный пример, который иллюстрировал бы как применимый вариант использования, так и базовую механику async в таких пониманиях?


person Andrew Guy    schedule 20.02.2017    source источник
comment
Меня тоже интересуют асинхронные генераторы. Такое же поведение или другое?   -  person Sebastian Wozny    schedule 22.02.2017


Ответы (2)


Вы в основном спрашиваете, как цикл async for работает по сравнению с обычным циклом. То, что теперь вы можете использовать такой цикл в понимании списка, здесь не имеет никакого значения; это просто оптимизация, которая позволяет избежать повторных вызовов list.append(), точно так же, как это делает обычное понимание списка.

Таким образом, цикл async for просто ожидает каждого следующего шага протокола итерации, где обычный цикл for блокировался бы.

Для иллюстрации представьте обычный цикл for:

for foo in bar:
    ...

Для этого цикла Python по существу делает следующее:

bar_iter = iter(bar)
while True:
    try:
        foo = next(bar_iter)
    except StopIteration:
        break
    ...

Вызов next(bar_iter) не является асинхронным; он блокирует.

Теперь замените for на async for, и то, что делает Python, меняется на:

bar_iter = aiter(bar)  # aiter doesn't exist, but see below
while True:
    try:
        foo = await anext(bar_iter)  # anext doesn't exist, but see below
    except StopIteration:
        break
    ...

В приведенном выше примере aiter() и anext() — вымышленные функции; это функционально точные эквиваленты своих собратьев iter() и next(), но вместо __iter__ и __next__ в них используются __aiter__ и __anext__. То есть асинхронные хуки существуют для той же функциональности, но отличаются от их неасинхронных вариантов префиксом a.

Ключевое слово await имеет решающее значение, поэтому для каждой итерации цикл async for дает управление, поэтому вместо него могут выполняться другие сопрограммы.

Опять же, чтобы повторить, все это уже было добавлено в Python 3.5 (см. " rel="noreferrer">PEP 492), все новое в Python 3.6 заключается в том, что вы можете использовать такой цикл и в понимании списка. И в генераторных выражениях, и в наборах и словах, если уж на то пошло.

И последнее, но не менее важное: тот же набор изменений также позволил использовать await <expression> в разделе выражения понимания, поэтому:

[await func(i) for i in someiterable]

теперь можно.

person Martijn Pieters    schedule 23.02.2017
comment
Спасибо Martijn за подробный ответ. Таким образом, цикл async for ведет себя так же, как обычный цикл for, за исключением того, что управление итерацией цикла передается включающей сопрограмме? Мне нужно будет тщательно рассмотреть использование сопрограмм, но это имеет гораздо больше смысла. - person Andrew Guy; 01.03.2017

Думаю, я понимаю, что функция aiter() вызывается асинхронно, так что каждая итерация aiter может выполняться без обязательного возврата предыдущей (или это неверное понимание?).

Такое понимание неверно. Итерации цикла async for не могут выполняться параллельно. async for так же последователен, как и обычный цикл for.

Асинхронная часть async for заключается в том, что он позволяет итератору await от имени сопрограммы перебирать его. Он предназначен только для использования в асинхронных сопрограммах и только для использования в специальных асинхронных итерируемых объектах. Кроме того, это в основном похоже на обычный цикл for.

person user2357112 supports Monica    schedule 22.02.2017
comment
Спасибо, кажется, мне нужно было пойти и правильно понять сопрограммы, прежде чем пытаться обдумать использование async. Я ценю поправку. :) - person Andrew Guy; 01.03.2017