PEP 718: Subscriptable functions (#3179)

Co-authored-by: Chris Angelico <rosuav@gmail.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: David Foster <david@dafoster.net>
This commit is contained in:
James Hilton-Balfe 2023-07-31 15:09:26 +01:00 committed by GitHub
parent 1453e26c74
commit 16e35f537f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 186 additions and 0 deletions

1
.github/CODEOWNERS vendored
View File

@ -596,6 +596,7 @@ pep-0712.rst @ericvsmith
pep-0713.rst @ambv
pep-0714.rst @dstufft
pep-0715.rst @dstufft
pep-0718.rst @gvanrossum
pep-0719.rst @Yhg1s
pep-0720.rst @FFY00
pep-0721.rst @encukou

185
pep-0718.rst Normal file
View File

@ -0,0 +1,185 @@
PEP: 718
Title: Subscriptable functions
Author: James Hilton-Balfe <gobot1234yt@gmail.com>
Sponsor: Guido van Rossum <guido@python.org>
Discussions-To: https://discuss.python.org/t/26463/
Status: Draft
Type: Standards Track
Topic: Typing
Content-Type: text/x-rst
Created: 23-Jun-2023
Python-Version: 3.13
Post-History: `24-Jun-2023 <https://discuss.python.org/t/28457/>`__
Abstract
--------
This PEP proposes making function objects subscriptable for typing purposes. Doing so
gives developers explicit control over the types produced by the type checker where
bi-directional inference (which allows for the types of parameters of anonymous
functions to be inferred) and other methods than specialisation are insufficient.
Motivation
----------
Currently, it is not possible to infer the type parameters to generic functions in
certain situations:
.. code-block:: python
def make_list[T](*args: T) -> list[T]: ...
reveal_type(make_list()) # type checker cannot infer a meaningful type for T
Making instances of ``FunctionType`` subscriptable would allow for this constructor to
be typed:
.. code-block:: python
reveal_type(make_list[int]()) # type is list[int]
Currently you have to use an assignment to provide a precise type:
.. code-block:: python
x: list[int] = make_list()
reveal_type(x) # type is list[int]
but this code is unnecessarily verbose taking up multiple lines for a simple function
call.
Similarly, ``T`` in this example cannot currently be meaningfully inferred, so ``x`` is
untyped without an extra assignment:
.. code-block:: python
def factory[T](func: Callable[[T], Any]) -> Foo[T]: ...
reveal_type(factory(lambda x: "Hello World" * x))
If function objects were subscriptable, however, a more specific type could be given:
.. code-block:: python
reveal_type(factory[int](lambda x: "Hello World" * x)) # type is Foo[int]
Currently, with unspecialised literals, it is not possible to determine a type for
situations similar to:
.. code-block:: python
def foo[T](x: list[T]) -> T: ...
reveal_type(foo([])) # type checker cannot infer T (yet again)
.. code-block:: python
reveal_type(foo[int]([])) # type is int
It is also useful to be able to specify in cases in which a certain type must be passed
to a function beforehand:
.. code-block:: python
words = ["hello", "world"]
foo[int](words) # Invalid: list[str] is incompatible with list[int]
Allowing subscription makes functions and methods consistent with generic classes where
they weren't already. Whilst all of the proposed changes can be implemented using
callable generic classes, syntactic sugar would be highly welcome.
Due to this, specialising the function and using it as a new factory is fine
.. code-block:: python
make_int_list = make_list[int]
reveal_type(make_int_list()) # type is list[int]
This proposal also opens the door to
`monomorphisation <https://en.wikipedia.org/wiki/Monomorphization>`_ and
`reified types <https://en.wikipedia.org/wiki/Reification_(computer_science)>`_
Rationale
---------
Function objects in this PEP is used to refer to ``FunctionType``\ , ``MethodType``\ ,
``BuiltinFunctionType``\ , ``BuiltinMethodType`` and ``MethodWrapperType``\ .
For ``MethodType`` you should be able to write:
.. code-block:: python
class Foo:
def make_list[T](self, *args: T) -> list[T]: ...
Foo().make_list[int]()
and have it work similarly to a ``FunctionType``.
For ``BuiltinFunctionType``, so builtin generic functions (e.g. ``max`` and ``min``)
work like ones defined in Python. Built-in functions should behave as much like
functions implemented in Python as possible.
``BuiltinMethodType`` is the same type as ``BuiltinFunctionType``.
``MethodWrapperType`` (e.g. the type of ``object().__str__``) is useful for
generic magic methods.
Specification
-------------
Function objects should implement ``__getitem__`` to allow for subscription at runtime
and return an instance of ``types.GenericAlias`` with ``__origin__`` set as the
callable and ``__args__`` as the types passed.
Type checkers should support subscripting functions and understand that the parameters
passed to the function subscription should follow the same rules as a generic callable
class.
Setting ``__orig_class__``
^^^^^^^^^^^^^^^^^^^^^^^^^^
Currently, ``__orig_class__`` is an attribute set in ``GenericAlias.__call__`` to the
instance of the ``GenericAlias`` that created the called class e.g.
.. code-block:: python
class Foo[T]: ...
assert Foo[int]().__orig_class__ == Foo[int]
Currently, ``__orig_class__`` is unconditionally set; however, to avoid potential
erasure on any created instances, this attribute should not be set if ``__origin__`` is
an instance of any function object.
The following code snippet would fail at runtime without this change as
``__orig_class__`` would be ``bar[str]`` and not ``Foo[int]``.
.. code-block:: python
def bar[U]():
return Foo[int]()
assert bar[str]().__orig_class__ is Foo[int]
Backwards Compatibility
-----------------------
Currently these classes are not subclassable and so there are no backwards
compatibility concerns with regards to classes already implementing
``__getitem__``.
Reference Implementation
------------------------
The runtime changes proposed can be found here
https://github.com/Gobot1234/cpython/tree/function-subscript
Acknowledgements
----------------
Thank you to Alex Waygood and Jelle Zijlstra for their feedback on this PEP and Guido
for some motivating examples.
Copyright
---------
This document is placed in the public domain or under the CC0-1.0-Universal license,
whichever is more permissive.