Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-94912: Added marker for non-standard coroutine function detection#99247

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
gvanrossum merged 16 commits intopython:mainfromcarltongibson:fix-issue-XXXXX
Dec 18, 2022
Merged
Changes from1 commit
Commits
Show all changes
16 commits
Select commitHold shift + click to select a range
4cf8e98
Added initial test and docs drafts.
carltongibsonNov 8, 2022
6b8fa87
Make first case pass setting code flags.
carltongibsonNov 15, 2022
fa22a16
Adjust iscoroutinefunction() to look for async __call__.
carltongibsonNov 15, 2022
513d358
📜🤖 Added by blurb_it.
blurb-it[bot]Nov 17, 2022
397a975
Moved to decorator; extra tests.
carltongibsonNov 17, 2022
d156eab
Added tests for lambda, @classmethod, and @staticmethod cases.
carltongibsonNov 23, 2022
34859de
Adjusted docs signature for review comment.
carltongibsonNov 23, 2022
47a9fea
Improve grammar in NEWS file
gvanrossumNov 23, 2022
cd6c491
Adjusted docs.
carltongibsonNov 29, 2022
629dd81
Updated for review comments.
carltongibsonNov 30, 2022
a518c0f
Merge branch 'main' into fix-issue-XXXXX
gvanrossumNov 30, 2022
c6d2b88
Use marker for implementation.
carltongibsonDec 6, 2022
b7249a8
Added "What's New..." entry.
carltongibsonDec 6, 2022
3bc72e2
Adjusted implementation.
carltongibsonDec 8, 2022
c073cf7
Renamed to _is_coroutine_marker.
carltongibsonDec 13, 2022
5ffba32
Adjusted implementation and docs.
carltongibsonDec 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
Use marker for implementation.
  • Loading branch information
@carltongibson
carltongibson committedDec 6, 2022
commitc6d2b88a1884039c8ea98124401acbf10accf660
17 changes: 13 additions & 4 deletionsLib/inspect.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -392,22 +392,31 @@ def isgeneratorfunction(obj):
See help(isfunction) for a list of attributes."""
return _has_code_flag(obj, CO_GENERATOR)

# A marker for markcoroutinefunction and iscoroutinefunction.
_is_coroutine = object()

def markcoroutinefunction(func):
"""
Decorator to ensure callable is recognised as a coroutine function.
"""
if hasattr(func, '__func__'):
func = func.__func__
func.__code__ = func.__code__.replace(
co_flags=func.__code__.co_flags | CO_COROUTINE
)
func._is_coroutine = _is_coroutine
return func

def iscoroutinefunction(obj):
"""Return true if the object is a coroutine function.

Coroutine functions are defined with "async def" syntax.
Coroutine functions are normally defined with "async def" syntax, but may
be marked via markcoroutinefunction.
"""
func = getattr(obj, "__func__", obj)
if getattr(func, "_is_coroutine", None) is _is_coroutine:
return True

if not isclass(obj) and callable(obj) and getattr(obj.__call__, "_is_coroutine", None) is _is_coroutine:
return True
Copy link
ContributorAuthor

@carltongibsoncarltongibsonDec 6, 2022
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

For the check, we need to check plain functions and the__func__ cases,and the__call__ implementation for class instances. (Tests cover that: given that functions have a__call__ we need to be careful not to just check the one case.)

I looked at various ways of combining this into a single pass, but none that I found were particularly clear. Happy to take a suggestion, but I don't see this as being performance sensitive.

The implementation here is more sophisticated that the olderasyncio one…

https://github.com/python/cpython/blob/3.11/Lib/asyncio/coroutines.py#L17-L24

… but (as per the added test cases) various extra (seemingly legitimate) cases are now covered. (So 👍 I guess)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm thinking about this, but being distracted by other stuff. I'll try to get to it later this week.

carltongibson reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Hey@gvanrossum — thanks.

I thought one maybe neater re-write looks like this:

diff --git a/Lib/inspect.py b/Lib/inspect.pyindex 883d0b02bf..3b9bd14a38 100644--- a/Lib/inspect.py+++ b/Lib/inspect.py@@ -410,12 +410,13 @@ def iscoroutinefunction(obj):     Coroutine functions are normally defined with "async def" syntax, but may     be marked via markcoroutinefunction.     """-    func = getattr(obj, "__func__", obj)-    if getattr(func, "_is_coroutine", None) is _is_coroutine:-        return True--    if not isclass(obj) and callable(obj) and getattr(obj.__call__, "_is_coroutine", None) is _is_coroutine:-        return True+    if not isclass(obj) and callable(obj):+        # Test both the function and the __call__ implementation for the+        # _is_coroutine marker.+        f = getattr(getattr(obj, "__func__", obj), "_is_coroutine", None)+        c = getattr(obj.__call__, "_is_coroutine", None)+        if f is _is_coroutine or c is _is_coroutine:+            return True      return _has_code_flag(obj, CO_COROUTINE) or (         not isclass(obj) and callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

So perhaps a confusing aspect is thateverything that's callable has a__call__ method. Also the presence of__func__ indicates it's a class or instance method.

At least, those things seem to be obfuscating what's going on a bit.

Also, looking at the code for_has_code_flag(), I wonder if we don't need the same logic for checking the_is_coroutine flag.

What would happen if we wrote a helper function like this?

def _has_coroutine_mark(f):    while ismethod(f):        f = f.__func__    f = functools._unwrap_partial(f)    if not (isfunction(f) or _signature_is_functionlike(f)):        return False    return getattr(f, "_is_coroutine", None) is _is_coroutine

(FWIW I wonder if the variable_is_coroutine shouldn't be renamed_is_coroutine_marker.)

Now, I'm not entirely sure why you're testing for__call__, when none of the otheris<something>function() predicates test for it. Maybe things would become simpler if we removed that feature?

We could then define our function like this:

def iscoroutinefunction(obj):    return _has_code_flag(obj, CO_COROUTINE) or _has_coroutine_mark(obj)

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Now, I'm not entirely sure why you're testing forcall

OK, so the issue is we have classes like this:

        class Cl:            async def __call__(self):                pass

... where we needinstances to be identified as async, andiscoroutinefunction doesn't (currently) pick it up.

Note that this is an actual, already-out-there, need. e.g.the Starlette framework has this exact check to work around this limitation.asgiref works around this limitation in its usage by marking the instance itself with the_is_coroutine marker.

Either way, I think it is desirable to returnTrue for class instance of classes such asCl with anasync def __call__()

So that's the reason for adding the additional... _has_code_flag(obj.__call__, ...) check.

In this PR, given that we were adding@staticmethod and@classmethod and so on, it seemed that we also should cover this kind of case:

        class Cl2:            @inspect.markcoroutinefunction            def __call__(self):                pass

Which leads to needing similar in the_is_coroutine_marker block.

The latter case is somewhat hypothetical... — it comes from trying to cover all the cases here, rather than from real-use™. To that extent I'd be happy to drop it, but I guess if we do there's an issue in X months time, saying "I need to do this". 🤔

I don't know the correct thing to say. (?)

(FWIW I wonder if the variable _is_coroutine shouldn't be renamed _is_coroutine_marker.)

Yep OK. +1

What would happen if we wrote a helper function like this?

Let me have a read.

Thanks so much for your thought and input on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Hum, I still feel that you're sneaking in a new feature under the radar. :-) What this PR is supposed to fix isjust to add the ability to mark a sync function as "morally async". IOW the behavior of

class C:    @inspect.markcoroutinefunction    def __call__(self): ...print(inspect.iscoroutinefunction(C())

should be the same as

class C:    async def __call__(self): ...print(inspect.iscoroutinefunction(C())

and since the latter printsFalse, I don't thinkthis PR has any business changing the output toTrue.

If you think the functionality used by Starlette deserves to be included in inspect, you should probably open a new issue for that first so it can be discussed properly. But I personally think this is overreaching (however, I am repeating myself).

Have you considered addingdef _has_coroutine_mark(f): as I suggested?

(Sorry to be such a pain. But once we let this in there's no way back, so I prefer to have the minimal necessary functionality only. Other core devs may differ though.)


return _has_code_flag(obj, CO_COROUTINE) or (
not isclass(obj) and callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE)
)
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp