Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not all generators containing await are compiled to async generators #114104

Closed
mniip opened this issue Jan 16, 2024 · 2 comments
Closed

Not all generators containing await are compiled to async generators #114104

mniip opened this issue Jan 16, 2024 · 2 comments
Labels
docs Documentation in the Doc dir

Comments

@mniip
Copy link

mniip commented Jan 16, 2024

Documentation

A generator of the form f(x) for x in await a first awaits a and then creates a regular sync generator, as can be seen in the following minimal reproducer:

import asyncio
async def foo():
    print("Entered foo")
    return [1, 2, 3]
async def main():
    gen = (x for x in await foo())
    print(gen)
    print(list(gen))
asyncio.run(main())
Entered foo
<generator object main.<locals>.<genexpr> at 0x7f8fce7f8110>
[1, 2, 3]

However the python language reference 6.2.8. Generator expressions states:

If a generator expression contains either async for clauses or await expressions it is called an asynchronous generator expression. An asynchronous generator expression returns a new asynchronous generator object, which is an asynchronous iterator (see Asynchronous Iterators).

This seems to suggest that the generator f(x) for x in await a does contain an await expression, so it should become an async_generator? However there is also:

However, the iterable expression in the leftmost for clause is immediately evaluated, so that an error produced by it will be emitted at the point where the generator expression is defined, rather than at the point where the first value is retrieved.

This seems to hint at an implementation detail that the leftmost for clause has special treatment and is always evaluated before the generator is constructed, but the interactions with await are unclear from this, especially whether this disqualifies the generator from becoming async.

I don't know whether this is an undocumented feature or a bug.

Linked PRs

@serhiy-storchaka
Copy link
Member

cc @gvanrossum

I think that it is easier to make the documentation more explicit. The iterable expression in the leftmost for clause is not included in a generator expression. It is evaluated (and implicit iter() is called) before entering a generator function, so it is not a part of the generator function and does not make it asynchronous.

We can, of cause, change the implementation, but what is the use case for this? If you want to make an asynchronous generator and await foo() in it, you can use the following workaround:

gen = (x for _ in [1] for x in await foo())

But it is more clear to write an inner function:

async def gen():
    for x in await foo():
        yield x

In this way you can even make an asynchronous generator that does not contain any await.

async def gen():
    for x in sync_foo():
        yield x

@gvanrossum
Copy link
Member

The behavior is as intended. If that's not clear from the docs, those doc should be updated.

hauntsaninja pushed a commit to python/mypy that referenced this issue Jun 30, 2024
Fixes #10534

This PR fixes a bug in typechecking asynchronous generators.

Mypy currently typechecks a generator/comprehension as `AsyncGenerator`
if the leftmost expression contains `await`, or if it contains an `async
for`.

However, there are other situations where we should get async generator:
If there is an `await` expression in any of the conditions or in any
sequence except for the leftmost one, the generator/comprehension should
also be typechecked as `AsyncGenerator`.

I've implemented this change in Mypy and added a test case to assert
this behavior. If I enter the test cases into a regular repl, I can
confirm that the runtime representation is generator/async_generator as
the test case expects.

According to the [language
reference](https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-comp_for):

> If a comprehension contains either async for clauses or await
expressions or other asynchronous comprehensions it is called an
asynchronous comprehension.

Confusingly, the documentation itself is actually not quite correct
either, as pointed out in
python/cpython#114104

Alongside this change, I've made a PR to update the docs to be more
precise: python/cpython#121175 has more details.
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Jul 1, 2024
…time behavior (pythonGH-121175)

(cherry picked from commit 91313af)

Co-authored-by: Danny Yang <yangdanny97@users.noreply.github.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Jul 1, 2024
…time behavior (pythonGH-121175)

(cherry picked from commit 91313af)

Co-authored-by: Danny Yang <yangdanny97@users.noreply.github.com>
kumaraditya303 pushed a commit that referenced this issue Jul 1, 2024
…ntime behavior (GH-121175) (#121235)

gh-114104: clarify asynchronous comprehension docs to match runtime behavior (GH-121175)
(cherry picked from commit 91313af)

Co-authored-by: Danny Yang <yangdanny97@users.noreply.github.com>
kumaraditya303 pushed a commit that referenced this issue Jul 1, 2024
…ntime behavior (GH-121175) (#121234)

gh-114104: clarify asynchronous comprehension docs to match runtime behavior (GH-121175)
(cherry picked from commit 91313af)

Co-authored-by: Danny Yang <yangdanny97@users.noreply.github.com>
Akasurde pushed a commit to Akasurde/cpython that referenced this issue Jul 3, 2024
noahbkim pushed a commit to hudson-trading/cpython that referenced this issue Jul 11, 2024
estyxx pushed a commit to estyxx/cpython that referenced this issue Jul 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir
Projects
None yet
Development

No branches or pull requests

4 participants