PEP 747: Require TypeExpr(X | Y) syntax to spell value of type TypeExpr[X | Y] (#3893)
This commit is contained in:
parent
1696887355
commit
a6fb1e8d5a
|
@ -442,27 +442,23 @@ so must be disambiguated based on its argument type:
|
||||||
|
|
||||||
**Union**: The type expression ``T1 | T2`` is ambiguous with
|
**Union**: The type expression ``T1 | T2`` is ambiguous with
|
||||||
the value ``int1 | int2``, ``set1 | set2``, ``dict1 | dict2``, and more,
|
the value ``int1 | int2``, ``set1 | set2``, ``dict1 | dict2``, and more,
|
||||||
so must be disambiguated based on its argument types:
|
so must use the explicit ``TypeExpr(...)`` syntax:
|
||||||
|
|
||||||
- As a value expression, ``x | y`` has type equal to the return type of ``type(x).__or__``
|
- Yes:
|
||||||
if ``type(x)`` overrides the ``__or__`` method.
|
|
||||||
|
|
||||||
- When ``x`` has type ``builtins.type``, ``types.GenericAlias``, or the
|
::
|
||||||
internal type of a typing special form, ``type(x).__or__`` has a return type
|
|
||||||
in the format ``TypeExpr[T1 | T2]``.
|
|
||||||
|
|
||||||
- As a value expression, ``x | y`` has type equal to the return type of ``type(y).__ror__``
|
if isassignable(value, TypeExpr(int | str)): ...
|
||||||
if ``type(y)`` overrides the ``__ror__`` method.
|
|
||||||
|
|
||||||
- When ``y`` has type ``builtins.type``, ``types.GenericAlias``, or the
|
- No:
|
||||||
internal type of a typing special form, ``type(y).__ror__`` has a return type
|
|
||||||
in the format ``TypeExpr[T1 | T2]``.
|
|
||||||
|
|
||||||
- As a value expression, ``x | y`` has type ``UnionType``
|
::
|
||||||
in all other situations.
|
|
||||||
|
|
||||||
- This rule is intended to be consistent with the preexisting fallback rule
|
if isassignable(value, int | str): ...
|
||||||
used by static type checkers.
|
|
||||||
|
Future PEPs may make it possible to recognize the value expression ``T1 | T2`` directly as an
|
||||||
|
implicit TypeExpr value and avoid the need to use the explicit ``TypeExpr(...)`` syntax,
|
||||||
|
but that work is :ref:`deferred for now <recognize_uniontype_as_implicit_typeexpr_value>`.
|
||||||
|
|
||||||
The **stringified type expression** ``"T"`` is ambiguous with both
|
The **stringified type expression** ``"T"`` is ambiguous with both
|
||||||
the stringified annotation expression ``"T"``
|
the stringified annotation expression ``"T"``
|
||||||
|
@ -551,16 +547,6 @@ but not the other way around:
|
||||||
- ``type[Any]`` is assignable to ``TypeExpr[Any]``. (But not the
|
- ``type[Any]`` is assignable to ``TypeExpr[Any]``. (But not the
|
||||||
other way around.)
|
other way around.)
|
||||||
|
|
||||||
Relationship with UnionType
|
|
||||||
'''''''''''''''''''''''''''
|
|
||||||
|
|
||||||
``TypeExpr[U]`` is a subtype of ``UnionType`` iff ``U`` is
|
|
||||||
the type expression ``X | Y | ...``:
|
|
||||||
|
|
||||||
- ``TypeExpr[X | Y | ...]`` is a subtype of ``UnionType``.
|
|
||||||
|
|
||||||
``UnionType`` is assignable to ``TypeExpr[Any]``.
|
|
||||||
|
|
||||||
Relationship with object
|
Relationship with object
|
||||||
''''''''''''''''''''''''
|
''''''''''''''''''''''''
|
||||||
|
|
||||||
|
@ -610,29 +596,6 @@ The following signatures related to type expressions introduce
|
||||||
- ``typing.cast``
|
- ``typing.cast``
|
||||||
- ``typing.assert_type``
|
- ``typing.assert_type``
|
||||||
|
|
||||||
The following signatures transforming union type expressions introduce
|
|
||||||
``TypeExpr`` where previously ``UnionType`` existed so that a more-precise
|
|
||||||
``TypeExpr`` type can be inferred:
|
|
||||||
|
|
||||||
- ``builtins.type[T].__or__``
|
|
||||||
|
|
||||||
- Old: ``def __or__(self, value: Any, /) -> types.UnionType: ...``
|
|
||||||
- New: ``def __or__[T2](self, value: TypeExpr[T2], /) -> TypeExpr[T | T2]: ...``
|
|
||||||
|
|
||||||
- ``builtins.type[T].__ror__``
|
|
||||||
|
|
||||||
- Old: ``def __ror__(self, value: Any, /) -> types.UnionType: ...``
|
|
||||||
- New: ``def __ror__[T1](self, value: TypeExpr[T1], /) -> TypeExpr[T1 | T]: ...``
|
|
||||||
|
|
||||||
- ``types.GenericAlias.{__or__,__ror__}``
|
|
||||||
- «the internal type of a typing special form»``.{__or__,__ror__}``
|
|
||||||
|
|
||||||
However the implementations of those methods continue to return ``UnionType``
|
|
||||||
instances at runtime so that runtime ``isinstance`` checks like
|
|
||||||
``isinstance('42', int | str)`` and ``isinstance(int | str, UnionType)``
|
|
||||||
continue to work.
|
|
||||||
|
|
||||||
|
|
||||||
Unchanged signatures
|
Unchanged signatures
|
||||||
''''''''''''''''''''
|
''''''''''''''''''''
|
||||||
|
|
||||||
|
@ -666,31 +629,11 @@ not propose those changes now:
|
||||||
|
|
||||||
- Returns annotation expressions
|
- Returns annotation expressions
|
||||||
|
|
||||||
The following signatures accepting union type expressions continue
|
|
||||||
to use ``UnionType``:
|
|
||||||
|
|
||||||
- ``builtins.isinstance``
|
|
||||||
- ``builtins.issubclass``
|
|
||||||
- ``typing.get_origin`` (used in an ``@overload``)
|
|
||||||
|
|
||||||
The following signatures transforming union type expressions continue
|
|
||||||
to use ``UnionType`` because it is not possible to infer a more-precise
|
|
||||||
``TypeExpr`` type:
|
|
||||||
|
|
||||||
- ``types.UnionType.{__or__,__ror__}``
|
|
||||||
|
|
||||||
|
|
||||||
Backwards Compatibility
|
Backwards Compatibility
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
As a value expression, ``X | Y`` previously had type ``UnionType`` (via :pep:`604`)
|
The rules for recognizing type expression objects
|
||||||
but this PEP gives it the more-precise static type ``TypeExpr[X | Y]``
|
|
||||||
(a subtype of ``UnionType``) while continuing to return a ``UnionType`` instance at runtime.
|
|
||||||
Preserving compability with ``UnionType`` is important because ``UnionType``
|
|
||||||
supports ``isinstance`` checks, unlike ``TypeExpr``, and existing code relies
|
|
||||||
on being able to perform those checks.
|
|
||||||
|
|
||||||
The rules for recognizing other kinds of type expression objects
|
|
||||||
in a value expression context were not previously defined, so static type checkers
|
in a value expression context were not previously defined, so static type checkers
|
||||||
`varied in what types were assigned <https://discuss.python.org/t/typeform-spelling-for-a-type-annotation-object-at-runtime/51435/34>`_
|
`varied in what types were assigned <https://discuss.python.org/t/typeform-spelling-for-a-type-annotation-object-at-runtime/51435/34>`_
|
||||||
to such objects. Existing programs manipulating type expression objects
|
to such objects. Existing programs manipulating type expression objects
|
||||||
|
@ -741,10 +684,16 @@ spell simple **class objects** like ``int``, ``str``, ``list``, or ``MyClass``.
|
||||||
including those with brackets (like ``list[int]``) or pipes (like ``int | None``),
|
including those with brackets (like ``list[int]``) or pipes (like ``int | None``),
|
||||||
and including special types like ``Any``, ``LiteralString``, or ``Never``.
|
and including special types like ``Any``, ``LiteralString``, or ``Never``.
|
||||||
|
|
||||||
A ``TypeExpr`` variable looks similar to a ``TypeAlias`` definition, but
|
A ``TypeExpr`` variable (``maybe_float: TypeExpr``) looks similar to
|
||||||
can only be used where a dynamic value is expected.
|
a ``TypeAlias`` definition (``MaybeFloat: TypeAlias``), but ``TypeExpr``
|
||||||
``TypeAlias`` (and the ``type`` statement) by contrast define a name that can
|
can only be used where a dynamic value is expected:
|
||||||
be used where a fixed type is expected:
|
|
||||||
|
- No:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
maybe_float: TypeExpr = float | None
|
||||||
|
def sqrt(n: float) -> maybe_float: ... # ERROR: Can't use TypeExpr value in a type annotation
|
||||||
|
|
||||||
- Okay, but discouraged in Python 3.12+:
|
- Okay, but discouraged in Python 3.12+:
|
||||||
|
|
||||||
|
@ -760,13 +709,6 @@ be used where a fixed type is expected:
|
||||||
type MaybeFloat = float | None
|
type MaybeFloat = float | None
|
||||||
def sqrt(n: float) -> MaybeFloat: ...
|
def sqrt(n: float) -> MaybeFloat: ...
|
||||||
|
|
||||||
- No:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
maybe_float: TypeExpr = float | None
|
|
||||||
def sqrt(n: float) -> maybe_float: ... # ERROR: Can't use TypeExpr value in a type annotation
|
|
||||||
|
|
||||||
It is uncommon for a programmer to define their *own* function which accepts
|
It is uncommon for a programmer to define their *own* function which accepts
|
||||||
a ``TypeExpr`` parameter or returns a ``TypeExpr`` value. Instead it is more common
|
a ``TypeExpr`` parameter or returns a ``TypeExpr`` value. Instead it is more common
|
||||||
for a programmer to pass a literal type expression to an *existing* function
|
for a programmer to pass a literal type expression to an *existing* function
|
||||||
|
@ -1072,6 +1014,20 @@ The example above could be more-straightforwardly written as the equivalent:
|
||||||
def checkcast(typx: TypeExpr[T], value: object) -> T:
|
def checkcast(typx: TypeExpr[T], value: object) -> T:
|
||||||
|
|
||||||
|
|
||||||
|
.. _recognize_uniontype_as_implicit_typeexpr_value:
|
||||||
|
|
||||||
|
Recognize (T1 | T2) as an implicit TypeExpr value
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
It would be nice if a value expression like ``int | str`` could be recognized
|
||||||
|
as an implicit ``TypeExpr`` value and be used directly in a context where a
|
||||||
|
``TypeExpr`` was expected. However making that possible would require making
|
||||||
|
changes to the rules that type checkers use for the ``|`` operator. These rules
|
||||||
|
are currently underspecified and would need to be make explicit first,
|
||||||
|
before making changes to them. The PEP author is not sufficently motivated to
|
||||||
|
take on that specification work at the time of writing.
|
||||||
|
|
||||||
|
|
||||||
Footnotes
|
Footnotes
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue