Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions Doc/deprecations/pending-removal-in-future.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,6 @@ although there is currently no date scheduled for their removal.

* :class:`typing.Text` (:gh:`92332`).

* The internal class ``typing._UnionGenericAlias`` is no longer used to implement
:class:`typing.Union`. To preserve compatibility with users using this private
class, a compatibility shim will be provided until at least Python 3.17. (Contributed by
Jelle Zijlstra in :gh:`105499`.)

* :class:`unittest.IsolatedAsyncioTestCase`: it is deprecated to return a value
that is not ``None`` from a test case.

Expand Down
6 changes: 3 additions & 3 deletions Doc/library/functools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ The :mod:`functools` module defines the following functions:
... for i, elem in enumerate(arg):
... print(i, elem)

:class:`typing.Union` can also be used::
:class:`types.UnionType` and :data:`typing.Union` can also be used::

>>> @fun.register
... def _(arg: int | float, verbose=False):
Expand Down Expand Up @@ -662,8 +662,8 @@ The :mod:`functools` module defines the following functions:
The :func:`~singledispatch.register` attribute now supports using type annotations.

.. versionchanged:: 3.11
The :func:`~singledispatch.register` attribute now supports
:class:`typing.Union` as a type annotation.
The :func:`~singledispatch.register` attribute now supports :class:`types.UnionType`
and :data:`typing.Union` as type annotations.


.. class:: singledispatchmethod(func)
Expand Down
21 changes: 8 additions & 13 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5649,7 +5649,7 @@ Union Type
A union object holds the value of the ``|`` (bitwise or) operation on
multiple :ref:`type objects <bltin-type-objects>`. These types are intended
primarily for :term:`type annotations <annotation>`. The union type expression
enables cleaner type hinting syntax compared to subscripting :class:`typing.Union`.
enables cleaner type hinting syntax compared to :data:`typing.Union`.

.. describe:: X | Y | ...

Expand Down Expand Up @@ -5685,10 +5685,9 @@ enables cleaner type hinting syntax compared to subscripting :class:`typing.Unio

int | str == str | int

* It creates instances of :class:`typing.Union`::
* It is compatible with :data:`typing.Union`::

int | str == typing.Union[int, str]
type(int | str) is typing.Union

* Optional types can be spelled as a union with ``None``::

Expand All @@ -5714,15 +5713,16 @@ enables cleaner type hinting syntax compared to subscripting :class:`typing.Unio
TypeError: isinstance() argument 2 cannot be a parameterized generic

The user-exposed type for the union object can be accessed from
:class:`typing.Union` and used for :func:`isinstance` checks::
:class:`types.UnionType` and used for :func:`isinstance` checks. An object cannot be
instantiated from the type::

>>> import typing
>>> isinstance(int | str, typing.Union)
>>> import types
>>> isinstance(int | str, types.UnionType)
True
>>> typing.Union()
>>> types.UnionType()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot create 'typing.Union' instances
TypeError: cannot create 'types.UnionType' instances

.. note::
The :meth:`!__or__` method for type objects was added to support the syntax
Expand All @@ -5749,11 +5749,6 @@ The user-exposed type for the union object can be accessed from

.. versionadded:: 3.10

.. versionchanged:: 3.14

Union objects are now instances of :class:`typing.Union`. Previously, they were instances
of :class:`types.UnionType`, which remains an alias for :class:`typing.Union`.


.. _typesother:

Expand Down
4 changes: 0 additions & 4 deletions Doc/library/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,6 @@ Standard names are defined for the following types:

.. versionadded:: 3.10

.. versionchanged:: 3.14

This is now an alias for :class:`typing.Union`.

.. class:: TracebackType(tb_next, tb_frame, tb_lasti, tb_lineno)

The type of traceback objects such as found in ``sys.exception().__traceback__``.
Expand Down
10 changes: 1 addition & 9 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ Special forms
These can be used as types in annotations. They all support subscription using
``[]``, but each has a unique syntax.

.. class:: Union
.. data:: Union

Union type; ``Union[X, Y]`` is equivalent to ``X | Y`` and means either X or Y.

Expand Down Expand Up @@ -1132,14 +1132,6 @@ These can be used as types in annotations. They all support subscription using
Unions can now be written as ``X | Y``. See
:ref:`union type expressions<types-union>`.

.. versionchanged:: 3.14
:class:`types.UnionType` is now an alias for :class:`Union`, and both
``Union[int, str]`` and ``int | str`` create instances of the same class.
To check whether an object is a ``Union`` at runtime, use
``isinstance(obj, Union)``. For compatibility with earlier versions of
Python, use
``get_origin(obj) is typing.Union or get_origin(obj) is types.UnionType``.

.. data:: Optional

``Optional[X]`` is equivalent to ``X | None`` (or ``Union[X, None]``).
Expand Down
4 changes: 2 additions & 2 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -723,10 +723,10 @@ PEP 604: New Type Union Operator

A new type union operator was introduced which enables the syntax ``X | Y``.
This provides a cleaner way of expressing 'either type X or type Y' instead of
using :class:`typing.Union`, especially in type hints.
using :data:`typing.Union`, especially in type hints.

In previous versions of Python, to apply a type hint for functions accepting
arguments of multiple types, :class:`typing.Union` was used::
arguments of multiple types, :data:`typing.Union` was used::

def square(number: Union[int, float]) -> Union[int, float]:
return number ** 2
Expand Down
2 changes: 1 addition & 1 deletion Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ functools
---------

* :func:`functools.singledispatch` now supports :class:`types.UnionType`
and :class:`typing.Union` as annotations to the dispatch argument.::
and :data:`typing.Union` as annotations to the dispatch argument.::

>>> from functools import singledispatch
>>> @singledispatch
Expand Down
52 changes: 0 additions & 52 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2071,56 +2071,9 @@ turtle
(Contributed by Marie Roald and Yngve Mardal Moe in :gh:`126350`.)


types
-----

* :class:`types.UnionType` is now an alias for :class:`typing.Union`.
See :ref:`below <whatsnew314-typing-union>` for more details.
(Contributed by Jelle Zijlstra in :gh:`105499`.)


typing
------

.. _whatsnew314-typing-union:

* :class:`types.UnionType` and :class:`typing.Union` are now aliases for each other,
meaning that both old-style unions (created with ``Union[int, str]``) and new-style
unions (``int | str``) now create instances of the same runtime type. This unifies
the behavior between the two syntaxes, but leads to some differences in behavior that
may affect users who introspect types at runtime:

- Both syntaxes for creating a union now produce the same string representation in
``repr()``. For example, ``repr(Union[int, str])``
is now ``"int | str"`` instead of ``"typing.Union[int, str]"``.
- Unions created using the old syntax are no longer cached. Previously, running
``Union[int, str]`` multiple times would return the same object
(``Union[int, str] is Union[int, str]`` would be ``True``), but now it will
return two different objects. Users should use ``==`` to compare unions for equality, not
``is``. New-style unions have never been cached this way.
This change could increase memory usage for some programs that use a large number of
unions created by subscripting ``typing.Union``. However, several factors offset this cost:
unions used in annotations are no longer evaluated by default in Python 3.14
because of :pep:`649`; an instance of :class:`types.UnionType` is
itself much smaller than the object returned by ``Union[]`` was on prior Python
versions; and removing the cache also saves some space. It is therefore
unlikely that this change will cause a significant increase in memory usage for most
users.
- Previously, old-style unions were implemented using the private class
``typing._UnionGenericAlias``. This class is no longer needed for the implementation,
but it has been retained for backward compatibility, with removal scheduled for Python
3.17. Users should use documented introspection helpers like :func:`typing.get_origin`
and :func:`typing.get_args` instead of relying on private implementation details.
- It is now possible to use :class:`typing.Union` itself in :func:`isinstance` checks.
For example, ``isinstance(int | str, typing.Union)`` will return ``True``; previously
this raised :exc:`TypeError`.
- The ``__args__`` attribute of :class:`typing.Union` objects is no longer writable.
- It is no longer possible to set any attributes on :class:`typing.Union` objects.
This only ever worked for dunder attributes on previous versions, was never
documented to work, and was subtly broken in many cases.

(Contributed by Jelle Zijlstra in :gh:`105499`.)

* :class:`typing.TypeAliasType` now supports star unpacking.


Expand Down Expand Up @@ -3189,11 +3142,6 @@ Changes in the Python API
This temporary change affects other threads.
(Contributed by Serhiy Storchaka in :gh:`69998`.)

* :class:`types.UnionType` is now an alias for :class:`typing.Union`,
causing changes in some behaviors.
See :ref:`above <whatsnew314-typing-union>` for more details.
(Contributed by Jelle Zijlstra in :gh:`105499`.)

* The runtime behavior of annotations has changed in various ways; see
:ref:`above <whatsnew314-pep649>` for details. While most code that interacts
with annotations should continue to work, some undocumented details may behave
Expand Down
1 change: 0 additions & 1 deletion Include/internal/pycore_unionobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ PyAPI_FUNC(PyObject *) _Py_union_type_or(PyObject *, PyObject *);
extern PyObject *_Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *);
extern PyObject *_Py_make_parameters(PyObject *);
extern PyObject *_Py_union_args(PyObject *self);
extern PyObject *_Py_union_from_tuple(PyObject *args);

#ifdef __cplusplus
}
Expand Down
6 changes: 4 additions & 2 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,12 @@ def __hash__(self):
))

def __or__(self, other):
return types.UnionType[self, other]
import typing
return typing.Union[self, other]

def __ror__(self, other):
return types.UnionType[other, self]
import typing
return typing.Union[other, self]

def __repr__(self):
extra = []
Expand Down
17 changes: 12 additions & 5 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,11 +929,16 @@ def dispatch(cls):
dispatch_cache[cls] = impl
return impl

def _is_union_type(cls):
from typing import get_origin, Union
return get_origin(cls) in {Union, UnionType}

def _is_valid_dispatch_type(cls):
if isinstance(cls, type):
return True
return (isinstance(cls, UnionType) and
all(isinstance(arg, type) for arg in cls.__args__))
from typing import get_args
return (_is_union_type(cls) and
all(isinstance(arg, type) for arg in get_args(cls)))

def register(cls, func=None):
"""generic_func.register(cls, func) -> func
Expand Down Expand Up @@ -965,7 +970,7 @@ def register(cls, func=None):
from annotationlib import Format, ForwardRef
argname, cls = next(iter(get_type_hints(func, format=Format.FORWARDREF).items()))
if not _is_valid_dispatch_type(cls):
if isinstance(cls, UnionType):
if _is_union_type(cls):
raise TypeError(
f"Invalid annotation for {argname!r}. "
f"{cls!r} not all arguments are classes."
Expand All @@ -981,8 +986,10 @@ def register(cls, func=None):
f"{cls!r} is not a class."
)

if isinstance(cls, UnionType):
for arg in cls.__args__:
if _is_union_type(cls):
from typing import get_args

for arg in get_args(cls):
registry[arg] = func
else:
registry[cls] = func
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ class UnionForwardrefs:
str | int,
)
union = annos["union"]
self.assertIsInstance(union, Union)
self.assertIs(typing.get_origin(union), Union)
# self.assertIsInstance(union, Union)
arg1, arg2 = typing.get_args(union)
self.assertIs(arg1, str)
self.assertEqual(
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2317,7 +2317,7 @@ def test_docstring_one_field_with_default_none(self):
class C:
x: Union[int, type(None)] = None

self.assertDocStrEqual(C.__doc__, "C(x:int|None=None)")
self.assertDocStrEqual(C.__doc__, "C(x:Optional[int]=None)")

def test_docstring_list_field(self):
@dataclass
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3203,7 +3203,7 @@ def _(arg: typing.Union[int, typing.Iterable[str]]):
"Invalid annotation for 'arg'."
)
self.assertEndsWith(str(exc.exception),
'int | typing.Iterable[str] not all arguments are classes.'
'typing.Union[int, typing.Iterable[str]] not all arguments are classes.'
)

def test_invalid_positional_argument(self):
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1779,14 +1779,14 @@ class C(metaclass=M):
class TestFormatAnnotation(unittest.TestCase):
def test_typing_replacement(self):
from test.typinganndata.ann_module9 import A, ann, ann1
self.assertEqual(inspect.formatannotation(ann), 'List[str] | int')
self.assertEqual(inspect.formatannotation(ann1), 'List[testModule.typing.A] | int')
self.assertEqual(inspect.formatannotation(ann), 'Union[List[str], int]')
self.assertEqual(inspect.formatannotation(ann1), 'Union[List[testModule.typing.A], int]')

self.assertEqual(inspect.formatannotation(A, 'testModule.typing'), 'A')
self.assertEqual(inspect.formatannotation(A, 'other'), 'testModule.typing.A')
self.assertEqual(
inspect.formatannotation(ann1, 'testModule.typing'),
'List[testModule.typing.A] | int',
'Union[List[testModule.typing.A], int]',
)

def test_forwardref(self):
Expand Down Expand Up @@ -1824,7 +1824,7 @@ class B: ...
# Not an instance of "type":
self.assertEqual(
inspect.formatannotationrelativeto(A)(ann1),
'List[testModule.typing.A] | int',
'Union[List[testModule.typing.A], int]',
)


Expand Down
16 changes: 8 additions & 8 deletions Lib/test/test_pydoc/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class C(builtins.object)
c_alias = test.test_pydoc.pydoc_mod.C[int]
list_alias1 = typing.List[int]
list_alias2 = list[int]
type_union1 = int | str
type_union1 = typing.Union[int, str]
type_union2 = int | str

VERSION
Expand Down Expand Up @@ -215,7 +215,7 @@ class C(builtins.object)
c_alias = test.test_pydoc.pydoc_mod.C[int]
list_alias1 = typing.List[int]
list_alias2 = list[int]
type_union1 = int | str
type_union1 = typing.Union[int, str]
type_union2 = int | str

Author
Expand Down Expand Up @@ -1444,17 +1444,17 @@ def test_generic_alias(self):
self.assertIn(list.__doc__.strip().splitlines()[0], doc)

def test_union_type(self):
self.assertEqual(pydoc.describe(typing.Union[int, str]), 'Union')
self.assertEqual(pydoc.describe(typing.Union[int, str]), '_UnionGenericAlias')
doc = pydoc.render_doc(typing.Union[int, str], renderer=pydoc.plaintext)
self.assertIn('Union in module typing', doc)
self.assertIn('class Union(builtins.object)', doc)
self.assertIn('_UnionGenericAlias in module typing', doc)
self.assertIn('Union = typing.Union', doc)
if typing.Union.__doc__:
self.assertIn(typing.Union.__doc__.strip().splitlines()[0], doc)

self.assertEqual(pydoc.describe(int | str), 'Union')
self.assertEqual(pydoc.describe(int | str), 'UnionType')
doc = pydoc.render_doc(int | str, renderer=pydoc.plaintext)
self.assertIn('Union in module typing', doc)
self.assertIn('class Union(builtins.object)', doc)
self.assertIn('UnionType in module types object', doc)
self.assertIn('\nclass UnionType(builtins.object)', doc)
if not MISSING_C_DOCSTRINGS:
self.assertIn(types.UnionType.__doc__.strip().splitlines()[0], doc)

Expand Down
Loading
Loading