we should update the typing spec to not refer to Callable as a special form, because unlike Union or Literal, it is just a normal class
type checkers might choose to apply some special casing to this type like saying it has a __get__ method, but i don’t think that necessitates it being a special form
currently the spec says that type[Callable] is invalid because Callable is regarded as a special form. i believe this provides no safety or clarity, and prevents valid typing patterns
Callable is no more of a special form than Iterable or any other class is
here is a proposed change to typeshed to correct the currently incorrect definition of typing.Callable:
here are some issues that are a result of this incorrect typing:
from collections.abc import Callable
from typing import _SpecialForm, assert_type
class C(Callable): ... # expect no error
a: Callable
a.__call__ # expect no error
s: _SpecialForm = Callable # expect an error
Callable._name # expect an error
assert_type(Callable, type[Callable]) # expect no error
class NotCallable: ...
_: type[Callable] = NotCallable # expect an error
But then it’d differ with typing.Callable in two ways:
@runtime_checkable isn’t powerful enough, so isinstance(_, Callable) could lead to false positives, for example for some user-defined callable class CallMe, both isinstance(CallMe, Callable) and isinstance(call_me_instance, Callable) would be true. In some niche cases where CallMe cannot be constructed as CallMe, this would be incorrect. I also expect it to be a bit slower this way.
InTs is a ParamSpec, but as far as I’m aware, ParamSpec is always invariant, and cannot be defined contravariant.
Personally, I’d be able to live with 1., but 2. can be pretty problematic. So unless ParamSpec can be defined as contravariant, I think that the better approach would indeed be to, as you suggested, to make the typing.Callable special form a bit less special, so that it’ll behave as if it’s the Callable protocol above, but without its two issues.
Callable is in fact currently a special form because it is listed as such in Type annotations — typing documentation . Whether it should be a special form is a different question. It would be nice if Callable can just be a regular Protocol with a __call__ method, but as discussed in this thread, we’re not quite there yet.