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:
parent
1453e26c74
commit
16e35f537f
|
@ -596,6 +596,7 @@ pep-0712.rst @ericvsmith
|
||||||
pep-0713.rst @ambv
|
pep-0713.rst @ambv
|
||||||
pep-0714.rst @dstufft
|
pep-0714.rst @dstufft
|
||||||
pep-0715.rst @dstufft
|
pep-0715.rst @dstufft
|
||||||
|
pep-0718.rst @gvanrossum
|
||||||
pep-0719.rst @Yhg1s
|
pep-0719.rst @Yhg1s
|
||||||
pep-0720.rst @FFY00
|
pep-0720.rst @FFY00
|
||||||
pep-0721.rst @encukou
|
pep-0721.rst @encukou
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue