GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `inspect.iscoroutinefunction()` does not detect marked partial objects · Issue #142418 · python/cpython

inspect.iscoroutinefunction() does not detect marked partial objects #142418

New issue
Copy link
New issue
Copy link
Open
#142503
Open
inspect.iscoroutinefunction() does not detect marked partial objects#142418
#142503
Copy link
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@x42005e1f
x42005e1f
opened on Dec 8, 2025
Issue body actions

Bug report

Bug description:

inspect.markcoroutinefunction() is applied directly to the passed function, except for methods (which is correct behavior). However, inspect.iscoroutinefunction() checks the passed function only after unwrapping (a function that is not wrapped in either functools.partial or functools.partialmethod (currently, _has_coroutine_mark() does not handle these objects; why?)), and thus cannot detect the marker that is not at the end of the unwrapping chain, which results in false negative in cases where a functools.partial/functools.partialmethod object is marked.

>>> from functools import partial
>>> from inspect import iscoroutinefunction, markcoroutinefunction
>>> async def wedonotlikesnakecase():
...     return "the_funniest_joke_in_the_world"
>>> def manufacturer_of_jokes(somefunc):
...     global manufacturer_of_jokes
...     del manufacturer_of_jokes
...     return somefunc()
>>> joke = partial(manufacturer_of_jokes, wedonotlikesnakecase)
>>> joke = markcoroutinefunction(joke)
>>> iscoroutinefunction(joke)
False

I discovered this problem while theoretically considering backporting inspect.iscoroutinefunction() to older versions of Python. The problem has not yet been caused by any use case, so it is okay if the issue is closed due to lack of demand. But just in case, I am attaching part of how iscoroutinefunction() is implemented in my code.

def iscoroutinefunction(obj):
    marker_name, marker_value = _get_coroutinefunction_marker()

    while True:  # unwrap & check
        if ismethod(obj):
            obj = obj.__func__
            continue

        if marker_value is not MISSING:
            if getattr(obj, marker_name, MISSING) is marker_value:
                return True

        if isinstance(obj, partial):
            obj = obj.func
            continue

        impl = getattr(obj, _partialmethod_attribute_name, MISSING)

        if isinstance(impl, partialmethod):
            obj = impl.func
            continue

        # unlike its namesake in the `inspect` module, it does not unwrap
        return _has_code_flag(obj, CO_COROUTINE)

CPython versions tested on:

3.12, 3.13, 3.14

Operating systems tested on:

Linux

Linked PRs

  • gh-142418: Fix inspect.iscoroutinefunction for marked functools.partial and partialmethod objects #142503
  • gh-142418 Enhance markcoroutinefunction to support functools.partial #142505

Activity

  1. x42005e1f
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Dec 8, 2025
  2. StanFromIreland
    added this to Inspect issues 🔍on Dec 8, 2025
  3. StanFromIreland
    added
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    on Dec 8, 2025
  4. x42005e1f commented on Dec 9, 2025

    @x42005e1f
    x42005e1f
    on Dec 9, 2025
    ContributorAuthor
    More actions

    Some thoughts on this issue.

    The inspect.is*function() family is quite... ambiguous. From the meaning of inspect.isfunction(), it can be logically deduced that an object for which inspect.iscoroutinefunction() returns True must also be a function object, and therefore have attributes such as __code__. This is also reinforced by the fact that in #99247, adding a check for classes that have async def __call__() was rejected. However, at the moment, inspect.iscoroutinefunction() handles the following cases:

    1. Coroutine function-like objects (for compatibility with Cython).
    2. functools.partial objects wrapping a coroutine function-like or a marked object.
    3. Regular functions defined by functools.partialmethod for a coroutine function-like.

    Can we say that inspect.iscoroutinefunction() is still a check for a coroutine function object? I think not, and that a "coroutine factory" is more appropriate here (where the broader is a "coroutine-like factory", which is true for any callable object that returns an object that passes isinstance(object, (types.CoroutineType, collections.abc.Coroutine)) - a bit redundant, but fast). However, the standard library does not introduce any functions for such objects, and users have to rely on inspect.iscoroutinefunction() in cases where they actually expect any callable object that returns something coroutine-like (note that we even use collections.abc.Coroutine in user-defined annotations!).

    The above explains why the lack of support for marked partial objects may be unexpected behavior. I would also add that it is expected that inspect.iscoroutinefunction() returns True for any object to which inspect.markcoroutinefunction() has been applied, which is currently not the case for functools.partial objects.

  5. Joshua-Ward1 commented on Dec 10, 2025

    @Joshua-Ward1
    Joshua-Ward1
    on Dec 10, 2025
    Contributor
    More actions

    Claiming this — I'm implementing a fix now.

  6. chaope commented on Dec 10, 2025

    @chaope
    chaope
    on Dec 10, 2025
    Contributor
    More actions

    Based on the PR that added this feature/marker, the purpose is to

    be able to continue to identify sync functions returning awaitables, without calling them to see that.

    Given that, I think this issue indeed reports a bug. The root cause is that markcoroutinefunction sets the mark on partial function object (joke), while iscoroutinefunction looks at the underlying function object of partial function object (manufacturer_of_jokes). The deeper issue is that markcoroutinefunction and iscoroutinefunction uses different code path to find the function object it operates on.

    In order to fix this bug, we can do it in two ways: fix the problem with minimum change or fix it more thoroughly, but riskier.

    • minimum change: add functools._unwrap_partial to markcoroutinefunction

      • PR: gh-142418 Enhance markcoroutinefunction to support functools.partial #142505
    • fix it more thoroughly: move the logic in _has_code_flag to a separate function and reuse it in markcoroutinefunction and _has_coroutine_mark

    def _find_underlying_function(f):
        f = functools._unwrap_partialmethod(f)
        while ismethod(f):
            f = f.__func__
        f = functools._unwrap_partial(f)
        return f
    
    def _has_code_flag(f, flag):
        f = _find_underlying_function(f)
        if not (isfunction(f) or _signature_is_functionlike(f))::
            return False
        return bool(f.__code__.co_flags & flag)
    
    def markcoroutinefunction(f):
        f = _find_underlying_function(f)
        f._is_coroutine_marker = _is_coroutine_mark
        return f
    
    def _has_coroutine_mark(f):
        f = _find_underlying_function(f)
        return getattr(f, "_is_coroutine_marker", None) is _is_coroutine_mark
    
    
    
  7. Joshua-Ward1
    added 3 commits that reference this issue on Dec 10, 2025

    pythongh-142418: Fix inspect.iscoroutinefunction for marked functools…

    1c4e6b9

    Fix NEWS entry formatting for pythongh-142418

    245a701

    pythongh-142418: Remove unnecessary cycle detection from _has_corouti…

    dba7cb9
  8. x42005e1f commented on Dec 10, 2025

    @x42005e1f
    x42005e1f
    on Dec 10, 2025
    ContributorAuthor
    More actions

    The deeper issue is that markcoroutinefunction and iscoroutinefunction uses different code path to find the function object it operates on.

    They should not use the same code path. manufacturer_of_jokes() accepts an arbitrary callable object (not necessarily a coroutine function), and therefore returns an arbitrary value. It should not be marked as a coroutine function.

  9. Joshua-Ward1
    added 2 commits that reference this issue on Dec 10, 2025

    pythongh-142418: Simplify _has_coroutine_mark by removing unnecessary…

    9961179

    pythongh-142418: Restore __partialmethod__ handling in _has_coroutine…

    1f05728
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    • Inspect issues 🔍
      Status
      No status
      Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.