Skip to content

inspect.is*function() overloads do not match the runtime behavior #15293

@x42005e1f

Description

@x42005e1f

TypeGuard-based overloads for inspect.is*function() were added in #8057, but they are somewhat controversial.

First, the first overload defines the return type via collections.abc.<Object>, while the rest define it via types.<Object>Type. As a result, type checkers infer the narrower type when the known return type differs from collections.abc.<Object>:

from collections.abc import Coroutine
from inspect import iscoroutinefunction
from types import CoroutineType
from typing import reveal_type

def native_coroutine_factory() -> CoroutineType[None, None, None]: ...
def coroutine_factory() -> Coroutine[None, None, None]: ...
def object_factory() -> object: ...

if iscoroutinefunction(native_coroutine_factory):
    reveal_type(native_coroutine_factory)
    # () -> CoroutineType[None, None, None]

if iscoroutinefunction(coroutine_factory):
    reveal_type(coroutine_factory)
    # () -> Coroutine[None, None, None]

if iscoroutinefunction(object_factory):
    reveal_type(object_factory)
    # () -> CoroutineType[Any, Any, Any]

Second, inspect.is*function() also returns True for functions that return a non-native object (for example, if the function was compiled via Cython), which means that the return type may not be types.<Object>Type:

#!/usr/bin/env python3

import inspect


def generator_function():
    yield


async def coroutine_function():
    pass


async def async_generator_function():
    yield


def main():
    # `True` on both CPython and Cython:
    print(inspect.isgeneratorfunction(generator_function))
    print(inspect.iscoroutinefunction(coroutine_function))
    print(inspect.isasyncgenfunction(async_generator_function))

    # `True` on CPython, `False` on Cython:
    print(inspect.isgenerator(generator_function()))
    print(inspect.iscoroutine(coro := coroutine_function()))
    print(inspect.isasyncgen(async_generator_function()))

    coro.close()  # to avoid `RuntimeWarning`

if __name__ == "__main__":
    main()

Third, I am also concerned about the Python core developer's statement that iscoroutinefunction() is basically defined for callable objects that return awaitable objects. Does this mean that we should use Awaitable as the return type instead of Coroutine for inspect.iscoroutinefunction() (since if users follow that definition, inspect.markcoroutinefunction() will be applied just as broadly, even though this somewhat contradicts the documentation)?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions