Summary
I propose adding different variants of Callable to typing or other relevant parts of the stdlib, which use the same syntax as Callable but allow differentiating between different kinds of callables which may have different semantics.
Motivation
Callable is relatively broad, which means type checkers and authors have to make assumptions which makes it harder to write correct types for many cases where Callable is currently used. The exact semantics of Callable have also grown over time, sometimes in different ways across type checkers.
There have been a relatively large number of discussions where people generally agree that types which differentiate between the different kinds/variants of callable would solve the respective issue and otherwise be desirable. My hope is that this proposal can serve as a place to collect various peoples opinions and concerns.
In no particular order:
- comment by @NeilGirdhar: Add PartialApplication · Issue #1372 · python/typing · GitHub with agreement from @Gobot1234
- thread by @blhsing Why does FunctionType.__get__ return FunctionType rather than MethodType when bound to a class?
- comment by @erictraut (When) should we assume callable types are method descriptors? - #9 by erictraut with agreement from @rtpg @duncathan @JukkaL
- comment by @Jelle The structural type of typing.Callable - #8 by Jelle
- comment by @Gobot1234 The structural type of typing.Callable · python/typing · Discussion #1385 · GitHub
- issue by @glyph callables are inferred to be descriptors when they shouldn't always be, protocols are inferred to not be descriptors when maybe they should, and mypy converts between them with no error · Issue #15189 · python/mypy · GitHub
This list is far from exhaustive, since there are many issues raised against the various type checkers where the broadness of Callable has caused issues/confusion/type-unsafety. The last link, the issue by Glyph, iteslf contains a list of issues that are related to this.
Proposal
The core proposal is to introduce multiple new types which, for the most part, work like Callable. They differ in some specifics, which allows type checkers to be more strict while users can be more specific, and design harder to abuse APIs. Nothing here should change how Callable is treated, to maintain compatibility.
Part of the goal of this proposal is to collect ideas and concerns, instead of asking for a strict list of new types. However, i’ve come up with what i think would be a good starting point:
Function: adefined, named, function.FunctionandLambdashould never apply to the same type.__name__: str(except<lambda>,)__qualname__: str(except<lambda>)__doc__: None | str__annotations__: dict[str, type|types.GenericAlias]
Lambda: a anonymous function defined using lambda syntax.__name__: Literal["<lambda>"]/__qualname__: Literal["<lambda>"]__doc__: Literal[None]__annotations__: dict[typing.Never, typing.Never](i’m unsure if Never is a good way to show “this dict cannot contain values”, sinceLiteral[{}]is not an option)
UnboundMethod: a callable that takes self as the first parameter, but is not bound to an instance yet. Calling it requires passingselfexplicitly or turning it into aBoundMethod.__get__: Callable[[SelfParam, type[SelfParam]], BoundMethod[OtherParams, Return]](or a protocol/type corresponding tomethod-wrapper)
BoundMethod: a callable that is already bound to an instance, so calling it does not require passing theselfparameter.__func__: UnboundMethod[Params, Return]__self__: typing.Self
ClassMethod: a callable that takesclsas the first parameter, and was defined on a class.__self__: type[typing.Self](unlikeBoundMethod)
Potential Alternatives
Some of these can be approximated in the form of Protocols. This has been a relatively common solution across the various issues around Callable, but there are some things a Protocol can’t currently be used for. Even if a Protocol is sufficient, writing it requires in-depth knowledge about the way python deals with functions/methods.
Many of the Callable variants i proposed have equivalents in types (MethodType, FunctionType, LambdaType). Making these usable with type params like Callable has been suggested before but since there were apparently some issues implementing that, i chose to focus on new typing types instead.