# \`Callable\` isn't a special form

**URL:** <https://discuss.python.org/t/callable-isnt-a-special-form/106407>\
**Category:** Typing\
**Tags:** typing, typing-spec\
**Created:** [March 6, 2026, 2:12am UTC](https://discuss.python.org/t/callable-isnt-a-special-form/106407 "2026-03-06T02:12:26Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![KotlinIsland](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kotlinisland/32/19433_2.png) [@KotlinIsland](https://discuss.python.org/u/KotlinIsland)\
**Post date:** [March 6, 2026, 2:12am UTC](https://discuss.python.org/t/callable-isnt-a-special-form/106407/1 "2026-03-06T02:12:27Z")

</div>

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

> <https://github.com/python/typing/pull/2207>
>
> see: https://discuss.python.org/t/callable-isnt-a-special-form/106407
> 
> \`Callab…le\` is a normal class, not a special form
> 
> additionally, there is no reason to disallow \`type\[Callable\]\`, i can understand the motivation for disallowing \`type\[Literal\[1\]\]\` (although i disagree with it), i can't see any reason why \`type\[Callable\]\` isn't allowed when \`type\[Iterable\]\` is
> 
> \- https://github.com/astral-sh/ruff/pull/23753

`Callable` is no more of a special form than `Iterable` or any other class is

> <https://github.com/python/typing/pull/2214>
>
> \- spawned from #2207
> discussion: https://discuss.python.org/t/callable-isnt-a-s…pecial-form/106407
> \- typeshed: https://github.com/python/typeshed/pull/14761
> 
> \`Callable\` is a normal class, it doesn't have any special meaning in the type system, it's the same as the definitions of \`Iterable\` etc

here is a proposed change to typeshed to correct the currently incorrect definition of `typing.Callable`:

> <https://github.com/python/typeshed/pull/14761>
>
> discuss: https://discuss.python.org/t/callable-isnt-a-special-form/106407
> 
> \`ty…ping.Callable\` is not a \`\_SpecialForm\`, it's a \`\_SpecialGenericAlias\` (represented with \`\_Alias\` in the pyi), and \`collections.abc.Callable\` is a class
> 
> currently all the ABCs in \`collections.abc\` (sans \`Callable\`) are declared as \`runtime\_checkable\` \`Protocol\`s. while this is not technically accurate, it does a good job of describing their runtime behaviour. all of these types have corresponding aliases in \`typing\`. i am suggesting that we change the current type definition of \`Callable\` to follow the same pattern as the rest of these definitions. this change would make the definitions of \`Callable\` more accurate and more consistent with the other definitions in \`typing\` and \`collections.abc\`
> 
> i am trying to address many issues with \`Callable\` that are either 100% special cased, or don't currently work:
> \`\`\`py
> 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
> \`\`\`
> 
> motivating example was that a \`ty\` maintiner rejected a suggestion to provide more accurate representation of \`Callable\`, and instead suggested fixing the problem in typeshed: 
> \- https://github.com/astral-sh/ty/issues/1215
> 
> corresponding typing spec pr: https://github.com/python/typing/pull/2207

here are some issues that are a result of this incorrect typing:

```py
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

```

---

<div class="post-metadata">

**Author:** ![jorenham](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jorenham/32/27806_2.png) [@jorenham](https://discuss.python.org/u/jorenham)\
**Post date:** [March 6, 2026, 9:14am UTC](https://discuss.python.org/t/callable-isnt-a-special-form/106407/2 "2026-03-06T09:14:52Z")

</div>

I think this makes a lot of sense. As I’ve said before: we should avoid special cases and aim for simplicity. So this is a +1 for me.

* * *

Ideally, we would be able to `Callable` ourselves as

```py
@runtime_checkable
class Callable[**InTs, OutT](Protocol):
    @staticmethod
    def __call__ (*args: InTs.args, **kwargs: InTs.kwargs) -> OutT: ...

```

But then it’d differ with `typing.Callable` in two ways:

1. `@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.
2. `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.

---

<div class="post-metadata">

**Author:** ![Eneg](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/eneg/32/18177_2.png) [@Eneg](https://discuss.python.org/u/Eneg)\
**Post date:** [March 6, 2026, 10:46am UTC](https://discuss.python.org/t/callable-isnt-a-special-form/106407/3 "2026-03-06T10:46:05Z")

</div>

There’s also a 3rd difference:

1. Currently, `ParamSpec` cannot be parametrized using an argument list containing a `TypeVarTuple`.  
[Parametrizing \*\*P with [\*Ts]](https://discuss.python.org/t/parametrizing-p-with-ts/82569)

This seems to be an underspecification on `ParamSpec` rather than `Callable`, but it impacts user-defined `Callable` protocols and aliases.

---

<div class="post-metadata">

**Author:** ![Jelle](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jelle/32/1049_2.png) [@Jelle](https://discuss.python.org/u/Jelle)\
**Post date:** [March 6, 2026, 3:17pm UTC](https://discuss.python.org/t/callable-isnt-a-special-form/106407/4 "2026-03-06T15:17:19Z")

</div>

The specific section you’re changing in your PR needs a bigger rework, as I mentioned in [Spec change: Clarify that `tuple` should not be prohibited as an argument to `type` - #7 by Jelle](https://discuss.python.org/t/spec-change-clarify-that-tuple-should-not-be-prohibited-as-an-argument-to-type/105590/7) . Certainly `type[Literal]` is more obviously nonsensical than `type[Callable]`.

Callable is in fact currently a special form because it is listed as such in [Type annotations — typing documentation](https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions) . 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.

---

<div class="post-metadata">

**Author:** ![KotlinIsland](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kotlinisland/32/19433_2.png) [@KotlinIsland](https://discuss.python.org/u/KotlinIsland)\
**Post date:** [March 9, 2026, 2:39am UTC](https://discuss.python.org/t/callable-isnt-a-special-form/106407/5 "2026-03-09T02:39:47Z")

</div>

> [@jorenham](#):
>
> `**InTs` is a `ParamSpec`, but as far as I’m aware, `ParamSpec` is always invariant, and cannot be defined contravariant.

> [@Parameter specifications should have variance](https://discuss.python.org/t/parameter-specifications-should-have-variance/106452):
>
> Code sample in [basedpyright playground](https://basedpyright.com/?typeCheckingMode=all&reportUnannotatedClassAttribute=false&code=GYJw9gtgBALgngBwJYDsDmUkQWEMoDCAhgDYlEBGJApgDRQAK4MYAxmCfSAK4oxbUA%2BqwAW1VgGtKNAFByAAjz4DhYydOozW5AM47CRFMTIBtAFRmAkigb0A8txgAVALoAKJmBbsSASgBcMlDBUPI6MET8rBDUMCJgACZBIQnUwFCCwqQkmW5mRCBoOv5Q1gwAdAVF9BYSAO5VxaU25fWNvlAAtAB8UA7OJeVDcqnpwACMbhES1DqCYBQAVuIwJcRG2SYmC8usMC70AHJgKNQuAcnB07OCqKsGG6YmdwdQx6cuUAC8sEQzczsVnIZKMoMAAExTP43QF7NbZDRbWH7I4nM4XEK-f63PjwsiI558V7vM7fLEwpYrYIAYigTkQ1CgCAKRBiMGoICgACIyvJ1sYSFzMPpUAA3ApIQwwegURzctzI3xCpD6FBeWBiKA6VmMoj6LluO5KoA) from typing import Callable, Protocol, runtime\_checkable @runtime\_checkable class CanCall[\*\*InP, OutT](Protocol): @staticmethod def \_\_call\_\_(\*args: InP.args, \*\*kwargs: InP.kwargs) -\> OutT: ... def f1(takes\_object: CanCall[[object], None]): takes\_int: CanCall[[int], None] = takes\_object def f2(takes\_object: Callable[[object], None]): takes\_int: Callable[[int], None] = takes\_object # Type parameter "InP@CanCall" is invariant, but "(obje…
