# PEP 718: subscriptable functions

**URL:** <https://discuss.python.org/t/pep-718-subscriptable-functions/28457>\
**Category:** PEPs\
**Tags:** typing, builtins\
**Created:** [June 24, 2023, 9:18pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457 "2023-06-24T21:18:14Z")\
**Posts on this page:** 13\
**Page:** 8

<div class="post-metadata">

**Author:** ![tmk](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmk/32/10935_2.png) [@tmk](https://discuss.python.org/u/tmk)\
**Post date:** [August 23, 2026, 1:50pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/143 "2026-08-23T13:50:40Z")

</div>

> [@mikeshardmind](#):
>
> I don’t think this pep needs to be stalled on non-function callables. Let something like functools.wraps handle forwarding getitem in the simplest case, provide something that covers common cases like partial application, even if it’s just a recipe for library authors to use, and let adoption demand drive any changes needed outside the standard library.
> 
> As long as we make doing the right thing easy enough, this shouldn’t be a major concern. Wrapping a function with something other than a function while still annotating the wrapping as returning a callable is already in the territory of “things that require special care”

So, I think what you’re claiming is that non-function callables are sufficiently rare that we can just treat all generic callables as functions and allow subscription? That doesn’t really match my experience. I constantly work with callables where I’m not sure whether it’s a function or not. Just as a random example with [jax](https://github.com/jax-ml/jax):

```python
>>> import jax
>>> @jax.jit
... def f(x):
... return x * 2
...
>>> from types import FunctionType
>>> isinstance(f, FunctionType)
False

```

I’m sure I could fine more.

---

<div class="post-metadata">

**Author:** ![duncathan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/duncathan/32/25521_2.png) [@duncathan](https://discuss.python.org/u/duncathan)\
**Post date:** [August 23, 2026, 2:19pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/144 "2026-08-23T14:19:58Z")

</div>

I believe you’re misunderstanding Michael here. The idea is to let these sorts of wrappers do manual forwarding as needing. `functools.wraps()` should be updated to forward ` __getitem__ ()`, and library code that implements something like it should be updated to match. In your jax example, that would mean jax updating `jax._src.util.wraps()` to forward ` __getitem__ ()`. The argument (as I understand it) is that this sort of wrapping is already something that takes significant consideration, and it’s not unreasonable to ask library authors to take a bit more consideration to handle this forwarding.

---

<div class="post-metadata">

**Author:** ![randolf-scholz](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/randolf-scholz/32/11123_2.png) [@randolf-scholz](https://discuss.python.org/u/randolf-scholz)\
**Post date:** [August 24, 2026, 9:41am UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/145 "2026-08-24T09:41:22Z")

</div>

> [@jorenham](#):
>
> Right now I agree that in case of `f[T]() -> T` type-checkers are right to complain: `T` is a free type-variable here, so it’s impossible to resolve it.

Just want to remark that there are legitimate use-cases for that, and the type-variable can be resolved if outer context is given:

```python
class nn_Module: ...

def instantiate_from_json[T](arg: dict) -> T:
    raise NotImplementedError

m: nn_Module = instantiate_from_json({"_target_" : "module.klass"})

```

But that just underlines the point that these solvability\[1\] issues should get raised at the call-site, not at the defintion.

* * *

1. And yes that totally is a word, mathematicians use it all the time 😉

---

<div class="post-metadata">

**Author:** ![Kroppeb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kroppeb/32/13044_2.png) [@Kroppeb](https://discuss.python.org/u/Kroppeb)\
**Post date:** [August 24, 2026, 2:05pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/146 "2026-08-24T14:05:38Z")

</div>

I’d argue that the correct type here would be `Any`, if this result is what you want.

---

<div class="post-metadata">

**Author:** ![tmr232](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmr232/32/22122_2.png) [@tmr232](https://discuss.python.org/u/tmr232)\
**Post date:** [August 28, 2026, 6:05am UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/147 "2026-08-28T06:05:23Z")

</div>

One concern that I have, and did not see it addressed in neither the PEP or the thread, is the ambiguity created with:

```python
class C:
    def __call__ [T](t: T) -> T:
        return t

    def __getitem__ (i):
        return i

c = C()
c[int](3)

```

I would _assume_ that this can be expanded to:

```python
c. __getitem__ (int)(3)

```

and not:

```python
c. __call__ [int](3)

```

as that would be a breaking change, but I think this needs to be specified.

Additionally, it _does_ mean that if this PEP is accepted, adding ` __getitem__ ` to a class that already implements a generic ` __call__ ` would be a breaking change, right?

---

<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:** [August 28, 2026, 9:08am UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/148 "2026-08-28T09:08:19Z")

</div>

You’re talking about arbitrary user-defined callable objects, but this spec is explicitly about callable _functions_, so it seems out of scope to me.

---

<div class="post-metadata">

**Author:** ![tmr232](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmr232/32/22122_2.png) [@tmr232](https://discuss.python.org/u/tmr232)\
**Post date:** [August 28, 2026, 10:18am UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/149 "2026-08-28T10:18:40Z")

</div>

`C` is a user-defined callable object, but `C. __call__ ` is a function. So I’m wondering about the interaction between the two if this is accepted.

---

<div class="post-metadata">

**Author:** ![tmk](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmk/32/10935_2.png) [@tmk](https://discuss.python.org/u/tmk)\
**Post date:** [August 28, 2026, 12:27pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/150 "2026-08-28T12:27:10Z")

</div>

> [@tmr232](#):
>
> Additionally, it _does_ mean that if this PEP is accepted, adding ` __getitem__ ` to a class that already implements a generic ` __call__ ` would be a breaking change, right?

No, because (as has been discussed extensively though you are excused for not having read 145 posts) the `[int]` syntax will only work on functions and not on callable objects. So, if a class with a generic ` __call__ ` does not implement ` __getitem__ ` then `c[int]()` will fail with a runtime error. And if the class does implement ` __getitem__ `, then `c[int]()` means you call whatever ` __getitem__ ` returns.

---

<div class="post-metadata">

**Author:** ![guido](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/guido/32/21_2.png) [@guido](https://discuss.python.org/u/guido)\
**Post date:** [October 20, 2026, 9:39pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/152 "2026-10-20T21:39:24Z")

</div>

There’s a [PR almost ready](https://github.com/python/peps/pull/5180)\[1\] that updates the PEP to take almost all of the feedback into account. You can already preview the [rendered output](https://pep-previews--5180.org.readthedocs.build/pep-0718/).

* * *

1. Waiting for a CLA signing

---

<div class="post-metadata">

**Author:** ![guido](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/guido/32/21_2.png) [@guido](https://discuss.python.org/u/guido)\
**Post date:** [October 20, 2026, 10:31pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/153 "2026-10-20T22:31:01Z")

</div>

The new version of this PEP leaves some important pushback from @Jelle and @carljm incompletely answered.

The problem is that only actual function objects support subscription, and a decorator whose signature is

```py
def deco(func: Callable[[int], int]) -> Callable[[int], int]: ...

```

does not say whether it returns a function object – only that it returns a callable object. And not all callables are subscriptable – in particular, objects with a ` __call__ ` method aren’t, unless their author also added a ` __getitem__ ` method (and if they did so before this PEP was accepted, it would surely do something unrelated).

The new version answers this by saying “yeah, we know, it’ll just be a runtime error, no big deal”. But Jelle and Carl don’t like this answer much.

A partial solution would be to make your decorator generic, like this:

```py
def deco[F: Callable](func: F) -> F: ...

```

This allows the type checker to realize that if the argument is a `types.FunctionType` instance (which all objects created by `def` and `lambda` are), the returned object is _also_ such an instance, and peace returns to the kingdom.

But if the return type preserves the “function-ness” but not the exact signature, or if it takes _any_ callable and wraps it with an actual function (`def`), this hack won’t work.

Jelle’s proposal is to make a change to typeshed, where the actual specification for `FunctionType` lives, to make it generic, so you can write things like this:

```py
def deco[**P, R](func: Callable[P, R]) -> FunctionType[P, R]:

    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        print("Calling", func. __name__ )
        return func(*args, **kwargs)

    return wrapper

```

(A ~~small~~ change to type checkers will be needed for this to work.)

We can then see `FunctionType` as a subtype of `Callable` that adds a bunch of additional dunder attributes, rather than a stand-alone object definition that happens to define ` __call__ ` (and adding ` __getitem__ ` per the new PEP version).

Obviously the same treatment should be applied to all function types supported by the PEP (`FunctionType`, `MethodType`,` BuiltinFunctionType`, `BuiltinMethodType`, and `MethodWrapperType`, all in `types`).

Thoughts? @Jelle, did I capture your idea correctly?

---

<div class="post-metadata">

**Author:** ![tmk](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmk/32/10935_2.png) [@tmk](https://discuss.python.org/u/tmk)\
**Post date:** [October 21, 2026, 11:05am UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/154 "2026-10-21T11:05:14Z")

</div>

That’s one solution to the problem, but wouldn’t it be nicer if we had a [minimal version of intersection types](https://discuss.python.org/t/would-a-reduced-form-of-intersection-types-get-into-the-spec-faster/107450) (one which is forward-compatible with a full version) such that we could write

```python
def deco[**P, R](func: Callable[P, R]) -> Callable[P, R] & types.FunctionType:

    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        print("Calling", func. __name__ )
        return func(*args, **kwargs)

    return wrapper

```

?

The only thing that a minimal version of intersection types would need in order to support this, is the intersection of a concrete type (`FunctionType`) and a protocol (`Callable`), which is, I believe, basically the least problematic part of intersection types.

The above already works with `ty`: [Playground | ty](https://play.ty.dev/c81cf550-69a3-40eb-8960-21508bf48882)

---

<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:** [October 21, 2026, 12:52pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/155 "2026-10-21T12:52:44Z")

</div>

Such a type would not have the same semantics, because FunctionType currently has an Any-typed ` __call__ `. Thus this creates a gradual intersection, which would allow more calls.

Generally I don’t think limited intersections are worth it. We need to think through the whole behavior, and limiting to smaller areas doesn’t make those cases go away.

---

<div class="post-metadata">

**Author:** ![guido](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/guido/32/21_2.png) [@guido](https://discuss.python.org/u/guido)\
**Post date:** [October 21, 2026, 2:36pm UTC](https://discuss.python.org/t/pep-718-subscriptable-functions/28457/156 "2026-10-21T14:36:04Z")

</div>

The new version of the PEP is merged. You can now read [PEP 718](https://peps.python.org/pep-0718/) on the official peps site.

[Previous page](https://discuss.python.org/t/pep-718-subscriptable-functions/28457.md?page=7)
