Skip to content

Strange behavior with await in a generator expression #76294

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

Closed
ilevkivskyi opened this issue Nov 22, 2017 · 6 comments
Closed

Strange behavior with await in a generator expression #76294

ilevkivskyi opened this issue Nov 22, 2017 · 6 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) topic-asyncio type-feature A feature request or enhancement

Comments

@ilevkivskyi
Copy link
Member

BPO 32113
Nosy @1st1, @ilevkivskyi

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2017-11-22.14:42:41.493>
labels = ['interpreter-core', '3.8', 'type-bug', '3.7']
title = 'Strange behavior with await in a generator expression'
updated_at = <Date 2020-07-25.22:43:12.738>
user = 'https://github.com/ilevkivskyi'

bugs.python.org fields:

activity = <Date 2020-07-25.22:43:12.738>
actor = 'Bryan Hu'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Interpreter Core']
creation = <Date 2017-11-22.14:42:41.493>
creator = 'levkivskyi'
dependencies = []
files = []
hgrepos = []
issue_num = 32113
keywords = []
message_count = 3.0
messages = ['306732', '306733', '306736']
nosy_count = 2.0
nosy_names = ['yselivanov', 'levkivskyi']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue32113'
versions = ['Python 3.6', 'Python 3.7', 'Python 3.8']

@ilevkivskyi
Copy link
Member Author

PEP-530 is not very clear about await in generator expressions. But when I try it, the error is a bit confusing:

>>> async def g(i):
...     print(i)
... 
>>> async def f():
...     result = list(await g(i) for i in range(3))
...     print(result)
... 
>>> f().send(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
TypeError: 'async_generator' object is not iterable

At the same time a (seemingly) equivalent list comprehension works fine:

>>> async def f():
...     result = [await g(i) for i in range(3)]
...     print(result)
... 
>>> f().send(None)
0
1
2
[None, None, None]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

I would say that the first case should either behave as a second one, or raise a syntax error.

Or is it actually an intended behavior?

@ilevkivskyi ilevkivskyi added 3.7 (EOL) end of life interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error labels Nov 22, 2017
@1st1
Copy link
Member

1st1 commented Nov 22, 2017

... result = list(await g(i) for i in range(3))

This is equivalent to this code:

  async def ait():
      for i in range(3):
          v = await g(i)
          yield v

  result = list(ait())

Where 'ait' is an async generator function. You can't iterate it with the regular 'for x in ...' syntax, and you can't pass it to functions that expect a synchronous iterator (such as 'list').

Similarly, with synchronous code:

  a = (i for i in range(3))
  a[0]
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: 'generator' object is not subscriptable

where '(' for ... ')' is another syntax for defining a synchronous generator.

... result = [await g(i) for i in range(3)]

This is equivalent to this code:

  result = []
  for i in range(3):
      v = await g(i)
      result.append(v)

I agree that PEP-530 is a bit vague about this and can be updated. I'll take a look into that.

Perhaps we can make the "TypeError: 'async_generator' object is not iterable" error message a bit clearer. Any ideas to improve it are welcome.

I would say that the first case should either behave as a second one, or raise a syntax error.

No, but we can improve error messages.

@ilevkivskyi
Copy link
Member Author

A first simple idea that comes to my mind is special-case async generators/iterators in PyObject_GetIter to say something like:

TypeError: asynchronous iterable can't be used where an iterable is expected

If it is possible to detect that an async generator is resulting from a generator expression, then we can say:

TypeError: asynchronous generator expression can't be used as an iterable

@BryanHu BryanHu mannequin added the 3.8 (EOL) end of life label Jul 25, 2020
@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
@ezio-melotti ezio-melotti moved this to Todo in asyncio Jul 17, 2022
@kumaraditya303 kumaraditya303 added type-feature A feature request or enhancement and removed type-bug An unexpected behavior, bug, or error 3.8 (EOL) end of life 3.7 (EOL) end of life labels Oct 18, 2022
@kumaraditya303
Copy link
Contributor

The existing error message looks fine to me. I am interested in @gvanrossum's opinion here.

@gvanrossum
Copy link
Member

gvanrossum commented Nov 27, 2022

The problem is not the error message -- it is the discrepancy between list(await g(i) for ...) vs. [await g(i) for ...].

Ivan is right that when comprehensions were unified with generator expressions in Python 3, the intention was that these two should be equivalent, so it's a bit strange that they are handled differently by PEP 530.

A list comprehension without await definitely doesn't have the semantics that Yury gave -- an implicit function is always created, to ensure that the control variable has its own scope.

The thing is that PEP 530 doesn't mention generator expressions in the section about how the presence of await changes the semantics -- it only mentions (list, set and dict) comprehensions.

Given that this is how the PEP defines things, I think it is too late to change, and in the end I agree that we should close the issue.

@kumaraditya303
Copy link
Contributor

Given that this is how the PEP defines things, I think it is too late to change, and in the end I agree that we should close the issue.

Agreed.

@kumaraditya303 kumaraditya303 closed this as not planned Won't fix, can't repro, duplicate, stale Dec 2, 2022
Repository owner moved this from Todo to Done in asyncio Dec 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) topic-asyncio type-feature A feature request or enhancement
Projects
Status: Done
Development

No branches or pull requests

5 participants