-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
gh-157361: Implement anext() in Python instead of C
#157362
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
base: main
Are you sure you want to change the base?
Changes from all commits
cdd7ca2
fcec7cf
0903f03
52befa6
9db7837
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||
| """Builtins implemented in Python. | ||||||
|
|
||||||
| This module is frozen into the interpreter and imported during startup, | ||||||
| before the import system exists. The names listed in ``__all__`` are | ||||||
| copied into the ``builtins`` module. | ||||||
| """ | ||||||
|
|
||||||
| __all__ = ['anext'] | ||||||
|
|
||||||
| _NOT_GIVEN = sentinel("_NOT_GIVEN") | ||||||
|
|
||||||
|
|
||||||
| def anext(async_iterator, default=_NOT_GIVEN, /): | ||||||
| """Return the next item from the async iterator. | ||||||
|
|
||||||
| If default is given and the async iterator is exhausted, | ||||||
| it is returned instead of raising StopAsyncIteration. | ||||||
| """ | ||||||
| cls = type(async_iterator) | ||||||
| try: | ||||||
| # Looked up on the type, like the C slot am_anext. | ||||||
| anext_method = cls.__anext__ | ||||||
| except AttributeError: | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strictly speaking, there is now a slight divergence where accessing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| raise TypeError( | ||||||
| f"'{cls.__name__}' object is not an async iterator" | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ) from None | ||||||
| awaitable = anext_method(async_iterator) | ||||||
| if default is _NOT_GIVEN: | ||||||
| return awaitable | ||||||
| return _anext_with_default(awaitable, default) | ||||||
|
|
||||||
|
|
||||||
| async def _anext_with_default(awaitable, default): | ||||||
| try: | ||||||
| return await awaitable | ||||||
| except StopAsyncIteration: | ||||||
| return default | ||||||
|
|
||||||
|
|
||||||
| for _name in __all__: | ||||||
| globals()[_name].__module__ = 'builtins' | ||||||
| del _name | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| import types | ||
| import unittest | ||
| import contextlib | ||
| import warnings | ||
|
|
||
| from test.support.import_helper import import_module | ||
| from test.support import gc_collect, requires_working_socket | ||
|
|
@@ -709,7 +710,16 @@ def test_send(): | |
| async def test_throw(): | ||
| p = ait_class() | ||
| obj = anext(p, "completed") | ||
| self.assertRaises(SyntaxError, obj.throw, SyntaxError) | ||
| with warnings.catch_warnings(): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this a change of behavior then? maybe document it as well?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is but not important, throwing or sending something to just created generator always errors so in practice no one does it. The important part is that it raises exception for that and it does. |
||
| # Throwing into the unstarted anext() coroutine leaves the | ||
| # inner __anext__() awaitable never awaited. | ||
| warnings.simplefilter("ignore", RuntimeWarning) | ||
| self.assertRaises(SyntaxError, obj.throw, SyntaxError) | ||
| if isinstance(p, types.AsyncGeneratorType): | ||
| # The never-run asend() already registered the async | ||
| # generator with the loop's finalizer; close it explicitly | ||
| # so no aclose() task is left pending at loop close. | ||
| await p.aclose() | ||
| return "completed" | ||
|
|
||
| result = self.loop.run_until_complete(test_throw()) | ||
|
|
@@ -1132,9 +1142,13 @@ async def agenfn(): | |
| yield 'aaa' | ||
|
|
||
| agen = agenfn() | ||
| with contextlib.closing(anext(agen, "default").__await__()) as g: | ||
| with self.assertRaises(MyError): | ||
| g.throw(MyError()) | ||
| with warnings.catch_warnings(): | ||
| # Throwing into the unstarted anext() coroutine leaves the | ||
| # inner asend() awaitable never awaited. | ||
| warnings.simplefilter("ignore", RuntimeWarning) | ||
| with contextlib.closing(anext(agen, "default").__await__()) as g: | ||
| with self.assertRaises(MyError): | ||
| g.throw(MyError()) | ||
|
|
||
| def run_test(test): | ||
| with self.subTest('pure-Python anext()'): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Implement :func:`anext` in Python instead of C, in a frozen ``_pybuiltins`` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it's a built-in change I think you should also mention it in What's New 3.16 because it's kind of a breaking change for anyone having a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll do that after I do the other ones as well like |
||
| module. The awaitable returned by ``anext(it, default)`` is now a plain | ||
| coroutine, so introspection tools such as :func:`asyncio.print_call_graph` | ||
| can see through it into :meth:`~object.__anext__`. | ||
Uh oh!
There was an error while loading. Please reload this page.