Repository navigation
inspect.iscoroutinefunction() does not detect marked partial objects #142418
Description
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 (currently, functools.partialmethod_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/ object is marked.functools.partialmethod
>>> 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)
FalseI 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
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsNo status
Activity
x42005e1f commentedon Dec 9, 2025
Some thoughts on this issue.
The
inspect.is*function()family is quite... ambiguous. From the meaning ofinspect.isfunction(), it can be logically deduced that an object for whichinspect.iscoroutinefunction()returnsTruemust 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 haveasync def __call__()was rejected. However, at the moment,inspect.iscoroutinefunction()handles the following cases:functools.partialobjects wrapping a coroutine function-like or a marked object.functools.partialmethodfor 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 passesisinstance(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 oninspect.iscoroutinefunction()in cases where they actually expect any callable object that returns something coroutine-like (note that we even usecollections.abc.Coroutinein 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()returnsTruefor any object to whichinspect.markcoroutinefunction()has been applied, which is currently not the case forfunctools.partialobjects.Joshua-Ward1 commentedon Dec 10, 2025
Claiming this — I'm implementing a fix now.
chaope commentedon Dec 10, 2025
Based on the PR that added this feature/marker, the purpose is to
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_partialtomarkcoroutinefunctionfix it more thoroughly: move the logic in
_has_code_flagto a separate function and reuse it inmarkcoroutinefunctionand_has_coroutine_markpythongh-142418: Fix inspect.iscoroutinefunction for marked functools…
Fix NEWS entry formatting for pythongh-142418
pythongh-142418: Remove unnecessary cycle detection from _has_corouti…
x42005e1f commentedon Dec 10, 2025
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.pythongh-142418: Simplify _has_coroutine_mark by removing unnecessary…
pythongh-142418: Restore __partialmethod__ handling in _has_coroutine…