Repository navigation
Conversation
Every coroutine object has an __await__ attribute, so the separate CoroutineType test never changes the result. Check the exact generator type first (for generator-based coroutines), then check for __await__. Plain values such as ints and strs now run two cheap checks instead of three. The async iterable list-field benchmark median goes from 1.183 ms to 1.115 ms (5.8% less time). Co-Authored-By: Aiden <aiden@weco.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
is_awaitablefaster; the executor calls it for every resolved and completed value.CoroutineTypetest, because every coroutine also has__await__.test_execute_async_iterable_list_fieldmedian: 1.183 ms -> 1.115 ms, 5.8% less time.PYTHONPATH=src python -m pytest tests -q: 3821 passed, 18 skipped.Refs #308
What & Why
Before, a plain value such as an int or a str always went through three checks.
The executor runs this function about twice per list item, so the checks add up.
Now the function checks the exact generator type first and then checks for
__await__.The
CoroutineTypetest is gone, becausehasattr(value, "__await__")is true for every coroutine.Generator-based coroutines (
types.coroutine) have no__await__, so they keep the code-flag check.GeneratorTypeandCoroutineTypecannot be subclassed, sotype(value) is GeneratorTypegives the same result asisinstance.A raising
__getattr__or property raises the same error as before.Results
test_execute_async_iterable_list_field(median)is_awaitable(1)is_awaitable(future)is_awaitable(coroutine)All numbers come from CPython 3.12.13 on Linux x86-64, with each run on one dedicated CPU core.
A direct call with a coroutine is slower, because
hasattrcosts more thanisinstance.In a full query this extra cost stays within normal variation, because awaiting the coroutine costs much more.
Validation
Run on the base commit and this change:
The full test suite passed (3821 passed, 18 skipped), and ruff reported no problems.
A separate script compared results for coroutines, generator-based coroutines, futures, plain generators, and objects with
__await__set in different ways.All results and raised errors were identical to the base version.
Why this is safe and easy to maintain
The patch changes one small function and makes it shorter.
It adds no helpers, no dependencies and no special cases.
Return values and raised errors stay unchanged.
tests/pyutils/test_is_awaitable.pyand the execution tests cover these cases.AI assistance
This PR was prepared with AI assistance from Aiden. Candidates were tracked with Weco Observe.
Weco share link: https://dashboard.weco.ai/share/8qRACU--9yKbBk3kfmhWMf-kG98KR1Jj