-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
Description
Bug report
Bug description:
There seems to be no consensus in the codebase on how to handle Cython objects. Both inspect.iscoroutinefunction() and inspect.iscoroutine() are defined in the documentation for async def functions and their objects, respectively. Therefore, the following code should be valid for them:
#!/usr/bin/env python3
import inspect
async def coroutine_function() -> None:
pass
def main() -> None:
print(inspect.iscoroutinefunction(coroutine_function)) # `True`
print(inspect.iscoroutine(coro := coroutine_function())) # `True`
coro.close() # to avoid `RuntimeWarning`
if __name__ == "__main__":
main()The same semantics are also expected by typeshed, which defines annotations as follows (and thus declares support for introspection):
...
@overload
def iscoroutinefunction(obj: object) -> TypeGuard[Callable[..., CoroutineType[Any, Any, Any]]]: ...
...
def iscoroutine(object: object) -> TypeIs[CoroutineType[Any, Any, Any]]: ...
...However, after cythonize -i -3, the code changes its behavior:
True # `inspect.iscoroutinefunction()`
False # `inspect.iscoroutine()`This is because Cython uses its own objects for compiled functions and generators/coroutines/asynchronous generators. inspect.is<object>() returns True only for uncompiled objects, as it relies on the types module, but inspect.is<object>function() relies on checking function flags and handles function-like objects in a special way (for compatibility with Cython). As a result, we get the contradictory "coroutine function does not return a coroutine".
The problem is even deeper than one might imagine:
- Cython objects do not actually correspond completely to their counterparts from CPython. See [BUG]
inspect.get*state()does not work for compiled objects on Python ≥3.11 cython/cython#7448. - Mypy avoids using the types module to infer the return type due to possible incompatibility with Cython. See mypy should use CoroutineType instead of Coroutine for return types of async def functions mypy#18635 and Allow
types.GeneratorTypeto be used instead ofcollections.abc.Generatoras the return type of generator functions mypy#20522. - If the user wants to handle objects regardless of whether the module has been compiled or not, they have to come up with their own way of checking objects. See Add a
collection.abc.FunctionABC for function-like objects #131983.
What if we want to define a universal decorator that works for both regular and asynchronous functions? We could use inspect.iscoroutinefunction(), but it has very limited detection (in particular, it cannot detect object with async def __call__()), and library developers will only be able to rely on inspect.markcoroutinefunction() after the Python 3.11's EOF. A frequently suggested alternative is to check the return value's type using inspect.isawaitable(), but this is actually shooting oneself in the foot, as it will lead to false positives for future-like objects (such as asyncio future objects, which are also awaitable). The most logical solution would be to use inspect.iscoroutine(), but it does not support Cython (and in fact, it is even less useful; see GrahamDumpleton/wrapt#236 (comment)).
It seems to me that the question of whether the standard library (and related tools) should be Cython-aware has not been fully explored, which leads to such contradictions. I marked the issue as a bug, since it violates the expected behavior for any consistent interpretation.
CPython versions tested on:
3.9, 3.10, 3.11, 3.12, 3.13, 3.14
Operating systems tested on:
Linux