diff --git a/Doc/deprecations/pending-removal-in-future.rst b/Doc/deprecations/pending-removal-in-future.rst index edb672ed8ad9a2..b840445d66badf 100644 --- a/Doc/deprecations/pending-removal-in-future.rst +++ b/Doc/deprecations/pending-removal-in-future.rst @@ -123,7 +123,7 @@ 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 + :data:`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`.) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index beec9b942afc0f..fa245681dff8b4 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -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:: + :data:`typing.Union` can also be used:: >>> @fun.register ... def _(arg: int | float, verbose=False): @@ -663,7 +663,7 @@ The :mod:`functools` module defines the following functions: .. versionchanged:: 3.11 The :func:`~singledispatch.register` attribute now supports - :class:`typing.Union` as a type annotation. + :data:`typing.Union` as a type annotation. .. class:: singledispatchmethod(func) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 90683c0b00d78a..7d75ec4e8861a6 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -5573,7 +5573,7 @@ Union Type A union object holds the value of the ``|`` (bitwise or) operation on multiple :ref:`type objects `. These types are intended primarily for :term:`type annotations `. The union type expression -enables cleaner type hinting syntax compared to subscripting :class:`typing.Union`. +enables cleaner type hinting syntax compared to subscripting :data:`typing.Union`. .. describe:: X | Y | ... @@ -5609,7 +5609,7 @@ enables cleaner type hinting syntax compared to subscripting :class:`typing.Unio int | str == str | int - * It creates instances of :class:`typing.Union`:: + * It creates instances of :class:`types.UnionType`:: int | str == typing.Union[int, str] type(int | str) is typing.Union @@ -5638,15 +5638,15 @@ 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:: - >>> 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 "", line 1, in - 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 @@ -5673,11 +5673,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: diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 207024a7619902..f152193ccd247d 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -316,7 +316,9 @@ Standard names are defined for the following types: .. versionchanged:: 3.14 - This is now an alias for :class:`typing.Union`. + Added read-only attributes :attr:`!__name__`, :attr:`!__qualname__` + and :attr:`!__origin__`. + .. class:: TracebackType(tb_next, tb_frame, tb_lasti, tb_lineno) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 69df09c779592a..b78bf4c171033a 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1086,7 +1086,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. @@ -1128,10 +1128,10 @@ These can be used as types in annotations. They all support subscription using :ref:`union type expressions`. .. 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 + Both ``Union[int, str]`` and ``int | str`` now create instances of + the same class, :class:`types.UnionType`. + To check whether an object is a union at runtime, use + ``isinstance(obj, types.UnionType)``. For compatibility with earlier versions of Python, use ``get_origin(obj) is typing.Union or get_origin(obj) is types.UnionType``. diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 1067601c652300..702ed23ed4544f 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -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 diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index abf9677fd9cac5..01efc13e4932e6 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 44ee2bbeb7761f..c0c334cb84a303 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -2089,8 +2089,8 @@ turtle types ----- -* :class:`types.UnionType` is now an alias for :class:`typing.Union`. - See :ref:`below ` for more details. +* Add read-only attributes :attr:`!__name__`, :attr:`!__qualname__` and + :attr:`!__origin__` for :class:`types.UnionType` instances. (Contributed by Jelle Zijlstra in :gh:`105499`.) @@ -2099,38 +2099,22 @@ 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 +* Both old-style unions (created with ``Union[int, str]``) and new-style + unions (``int | str``) now create instances of the same runtime type, + :class:`types.UnionType`. 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. + - The ``__args__`` attribute of :data:`typing.Union` objects is no longer writable. + - It is no longer possible to set any attributes on the :data:`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. @@ -2782,8 +2766,8 @@ 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. +* Subscription of :data:`typing.Union` now returns a :class:`types.UnionType` + instance, causing changes in some behaviors. See :ref:`above ` for more details. (Contributed by Jelle Zijlstra in :gh:`105499`.) diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index c83a1573ccd3d1..e805edda456be5 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -6,6 +6,7 @@ import keyword import sys import types +from _typing import _make_union __all__ = [ "Format", @@ -292,10 +293,10 @@ def __hash__(self): )) def __or__(self, other): - return types.UnionType[self, other] + return _make_union(self, other) def __ror__(self, other): - return types.UnionType[other, self] + return _make_union(other, self) def __repr__(self): extra = [] diff --git a/Lib/inspect.py b/Lib/inspect.py index 183e67fabf966e..024bdacd841f0d 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1333,7 +1333,8 @@ def getargvalues(frame): def formatannotation(annotation, base_module=None, *, quote_annotation_strings=True): if not quote_annotation_strings and isinstance(annotation, str): return annotation - if getattr(annotation, '__module__', None) == 'typing': + if (isinstance(annotation, types.UnionType) + or getattr(annotation, '__module__', None) == 'typing'): def repl(match): text = match.group() return text.removeprefix('typing.') diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py index ae0e73f08c5bd0..0ea1faa7d6809c 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -8,6 +8,7 @@ import itertools import pickle from string.templatelib import Template +import types import typing import unittest from annotationlib import ( @@ -137,7 +138,7 @@ class UnionForwardrefs: str | int, ) union = annos["union"] - self.assertIsInstance(union, Union) + self.assertIsInstance(union, types.UnionType) arg1, arg2 = typing.get_args(union) self.assertIs(arg1, str) self.assertEqual( diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 3b50ead00bdd31..ae59462d748291 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -1444,19 +1444,19 @@ 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]), 'UnionType') 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('UnionType in module types', doc) + self.assertIn('UnionType = 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) - if not MISSING_C_DOCSTRINGS: - self.assertIn(types.UnionType.__doc__.strip().splitlines()[0], doc) + self.assertIn('UnionType in module types', doc) + self.assertIn('UnionType = typing.Union', doc) + if typing.Union.__doc__: + self.assertIn(typing.Union.__doc__.strip().splitlines()[0], doc) def test_special_form(self): self.assertEqual(pydoc.describe(typing.NoReturn), '_SpecialForm') diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 9b0ae709d7968d..ff8870f2affc9f 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1161,9 +1161,9 @@ def test_or_type_operator_reference_cycle(self): def test_instantiation(self): check_disallow_instantiation(self, types.UnionType) - self.assertIs(int, types.UnionType[int]) - self.assertIs(int, types.UnionType[int, int]) - self.assertEqual(int | str, types.UnionType[int, str]) + self.assertIs(int, typing.Union[int]) + self.assertIs(int, typing.Union[int, int]) + self.assertEqual(int | str, typing.Union[int, str]) for obj in ( int | typing.ForwardRef("str"), diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b1615bbff383c2..4e2da6d0243ad3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -508,6 +508,8 @@ def test_cannot_instantiate_vars(self): TypeVar('A')() def test_bound_errors(self): + with self.assertRaises(TypeError): + TypeVar('X', bound=Union) with self.assertRaises(TypeError): TypeVar('X', bound=Optional) with self.assertRaises(TypeError): @@ -549,7 +551,7 @@ def test_var_substitution(self): def test_bad_var_substitution(self): T = TypeVar('T') bad_args = ( - (), (int, str), Optional, + (), (int, str), Union, Optional, Generic, Generic[T], Protocol, Protocol[T], Final, Final[int], ClassVar, ClassVar[int], ) @@ -2055,6 +2057,10 @@ def test_union_issubclass(self): self.assertNotIsSubclass(int, Union[Any, str]) def test_union_issubclass_type_error(self): + with self.assertRaises(TypeError): + issubclass(int, Union) + with self.assertRaises(TypeError): + issubclass(Union, int) with self.assertRaises(TypeError): issubclass(Union[int, str], int) with self.assertRaises(TypeError): @@ -2174,9 +2180,12 @@ def test_dir(self): def test_cannot_subclass(self): with self.assertRaisesRegex(TypeError, - r"type 'typing\.Union' is not an acceptable base type"): + r"Cannot subclass typing\.Union"): class C(Union): pass + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class D(type(Union)): + pass with self.assertRaisesRegex(TypeError, r'Cannot subclass int \| str'): class E(Union[int, str]): @@ -5191,6 +5200,8 @@ def __contains__(self, item): def test_fail_with_special_forms(self): with self.assertRaises(TypeError): List[Final] + with self.assertRaises(TypeError): + Tuple[Union] with self.assertRaises(TypeError): Tuple[Optional] with self.assertRaises(TypeError): @@ -5734,6 +5745,8 @@ def test_subclass_special_form(self): for obj in ( ClassVar[int], Final[int], + Union[int, float], + Optional[int], Literal[1, 2], Concatenate[int, ParamSpec("P")], TypeGuard[int], @@ -5765,7 +5778,7 @@ class A: __parameters__ = (T,) # Bare classes should be skipped for a in (List, list): - for b in (A, int, TypeVar, TypeVarTuple, ParamSpec, types.GenericAlias, Union): + for b in (A, int, TypeVar, TypeVarTuple, ParamSpec, types.GenericAlias, types.UnionType): with self.subTest(generic=a, sub=b): with self.assertRaisesRegex(TypeError, '.* is not a generic class'): a[b][str] @@ -5784,7 +5797,7 @@ class A: for s in (int, G, A, List, list, TypeVar, TypeVarTuple, ParamSpec, - types.GenericAlias, Union): + types.GenericAlias, types.UnionType): for t in Tuple, tuple: with self.subTest(tuple=t, sub=s): @@ -10452,6 +10465,7 @@ def test_special_attrs(self): typing.TypeGuard: 'TypeGuard', typing.TypeIs: 'TypeIs', typing.TypeVar: 'TypeVar', + typing.Union: 'Union', typing.Self: 'Self', # Subscripted special forms typing.Annotated[Any, "Annotation"]: 'Annotated', @@ -10462,11 +10476,11 @@ def test_special_attrs(self): typing.Literal[Any]: 'Literal', typing.Literal[1, 2]: 'Literal', typing.Literal[True, 2]: 'Literal', - typing.Optional[Any]: 'Union', + typing.Optional[Any]: 'UnionType', typing.TypeGuard[Any]: 'TypeGuard', typing.TypeIs[Any]: 'TypeIs', typing.Union[Any]: 'Any', - typing.Union[int, float]: 'Union', + typing.Union[int, float]: 'UnionType', # Incompatible special forms (tested in test_special_attrs2) # - typing.NewType('TypeName', Any) # - typing.ParamSpec('SpecialAttrsP') @@ -10477,14 +10491,12 @@ def test_special_attrs(self): with self.subTest(cls=cls): self.assertEqual(cls.__name__, name, str(cls)) self.assertEqual(cls.__qualname__, name, str(cls)) - self.assertEqual(cls.__module__, 'typing', str(cls)) + if not isinstance(cls, types.UnionType): + self.assertEqual(cls.__module__, 'typing', str(cls)) for proto in range(pickle.HIGHEST_PROTOCOL + 1): s = pickle.dumps(cls, proto) loaded = pickle.loads(s) - if isinstance(cls, Union): - self.assertEqual(cls, loaded) - else: - self.assertIs(cls, loaded) + self.assertIs(cls, loaded) TypeName = typing.NewType('SpecialAttrsTests.TypeName', Any) diff --git a/Lib/typing.py b/Lib/typing.py index f1455c273d31ca..aa7c54db61e995 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -27,10 +27,11 @@ import operator import sys import types -from types import GenericAlias +from types import GenericAlias, UnionType from _typing import ( _idfunc, + _make_union, TypeVar, ParamSpec, TypeVarTuple, @@ -38,7 +39,6 @@ ParamSpecKwargs, TypeAliasType, Generic, - Union, NoDefault, ) @@ -453,7 +453,7 @@ def _eval_type(t, globalns, localns, type_params, *, recursive_guard=frozenset() return evaluate_forward_ref(t, globals=globalns, locals=localns, type_params=type_params, owner=owner, _recursive_guard=recursive_guard, format=format) - if isinstance(t, (_GenericAlias, GenericAlias, Union)): + if isinstance(t, (_GenericAlias, GenericAlias, UnionType)): if isinstance(t, GenericAlias): args = tuple( _make_forward_ref(arg, parent_fwdref=parent_fwdref) if isinstance(arg, str) else arg @@ -473,7 +473,7 @@ def _eval_type(t, globalns, localns, type_params, *, recursive_guard=frozenset() return t if isinstance(t, GenericAlias): return _rebuild_generic_alias(t, ev_args) - if isinstance(t, Union): + if isinstance(t, UnionType): return functools.reduce(operator.or_, ev_args) else: return t.copy_with(ev_args) @@ -727,6 +727,33 @@ class FastConnector(Connection): item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True) return _GenericAlias(self, (item,)) +@_SpecialForm +def Union(self, parameters): + """Union type; Union[X, Y] means either X or Y. + On Python 3.10 and higher, the | operator + can also be used to denote unions; + X | Y means the same thing to the type checker as Union[X, Y]. + To define a union, use e.g. Union[int, str]. Details: + - The arguments must be types and there must be at least one. + - None as an argument is a special case and is replaced by + type(None). + - Unions of unions are flattened, e.g.:: + assert Union[Union[int, str], float] == Union[int, str, float] + - Unions of a single argument vanish, e.g.:: + assert Union[int] == int # The constructor actually returns int + - Redundant arguments are skipped, e.g.:: + assert Union[int, str, int] == Union[int, str] + - When comparing unions, the argument order is ignored, e.g.:: + assert Union[int, str] == Union[str, int] + - You cannot subclass or instantiate a union. + - You can use Optional[X] as a shorthand for Union[X, None]. + """ + if parameters == (): + raise TypeError("Cannot take a Union of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + return _make_union(*parameters) + @_SpecialForm def Optional(self, parameters): """Optional[X] is equivalent to Union[X, None].""" @@ -1638,12 +1665,12 @@ class _UnionGenericAliasMeta(type): def __instancecheck__(self, inst: object) -> bool: import warnings warnings._deprecated("_UnionGenericAlias", remove=(3, 17)) - return isinstance(inst, Union) + return isinstance(inst, UnionType) def __subclasscheck__(self, inst: type) -> bool: import warnings warnings._deprecated("_UnionGenericAlias", remove=(3, 17)) - return issubclass(inst, Union) + return issubclass(inst, UnionType) def __eq__(self, other): import warnings @@ -2413,7 +2440,7 @@ def _strip_annotations(t): if stripped_args == t.__args__: return t return _rebuild_generic_alias(t, stripped_args) - if isinstance(t, Union): + if isinstance(t, UnionType): stripped_args = tuple(_strip_annotations(a) for a in t.__args__) if stripped_args == t.__args__: return t @@ -2442,13 +2469,11 @@ def get_origin(tp): """ if isinstance(tp, _AnnotatedAlias): return Annotated - if isinstance(tp, (_BaseGenericAlias, GenericAlias, + if isinstance(tp, (_BaseGenericAlias, GenericAlias, UnionType, ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ if tp is Generic: return Generic - if isinstance(tp, Union): - return Union return None @@ -2473,7 +2498,7 @@ def get_args(tp): if _should_unflatten_callable_args(tp, res): res = (list(res[:-1]), res[-1]) return res - if isinstance(tp, Union): + if isinstance(tp, UnionType): return tp.__args__ return () diff --git a/Misc/NEWS.d/3.14.0a4.rst b/Misc/NEWS.d/3.14.0a4.rst index 176ba72da65e4b..be1ee9d1b798b8 100644 --- a/Misc/NEWS.d/3.14.0a4.rst +++ b/Misc/NEWS.d/3.14.0a4.rst @@ -164,8 +164,8 @@ with meta (i.e. :kbd:`Alt`), e.g. :kbd:`Alt-d` to ``kill-word`` or .. nonce: RIvgwc .. section: Library -Unify the instance check for :class:`typing.Union` and -:class:`types.UnionType`: :class:`!Union` now uses the instance checks +Unify the instance check for :data:`typing.Union` and +:class:`types.UnionType`: :data:`!Union` now uses the instance checks against its parameters instead of the subclass checks. .. diff --git a/Misc/NEWS.d/3.14.0a6.rst b/Misc/NEWS.d/3.14.0a6.rst index d8840b6f283e76..e330db28586dcd 100644 --- a/Misc/NEWS.d/3.14.0a6.rst +++ b/Misc/NEWS.d/3.14.0a6.rst @@ -834,7 +834,7 @@ performance. Patch by Romain Morotti. .. nonce: 7jV6cP .. section: Library -Make :class:`types.UnionType` an alias for :class:`typing.Union`. Both ``int +[Partially reverted in :gh:`137065`] Make :class:`types.UnionType` an alias for :data:`typing.Union`. Both ``int | str`` and ``Union[int, str]`` now create instances of the same type. Patch by Jelle Zijlstra. diff --git a/Misc/NEWS.d/3.14.0b1.rst b/Misc/NEWS.d/3.14.0b1.rst index 5d03d429f9ee14..ddeda4c5e6004d 100644 --- a/Misc/NEWS.d/3.14.0b1.rst +++ b/Misc/NEWS.d/3.14.0b1.rst @@ -407,7 +407,7 @@ Speedup pasting in ``PyREPL`` on Windows. Fix by Chris Eibl. .. nonce: 6zoyp5 .. section: Library -Fix copying of :class:`typing.Union` objects containing objects that do not +Fix copying of :data:`typing.Union` objects containing objects that do not support the ``|`` operator. .. diff --git a/Misc/NEWS.d/next/Library/2025-07-24-11-19-24.gh-issue-137065.AALPF4.rst b/Misc/NEWS.d/next/Library/2025-07-24-11-19-24.gh-issue-137065.AALPF4.rst new file mode 100644 index 00000000000000..835dc4d25c317a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-24-11-19-24.gh-issue-137065.AALPF4.rst @@ -0,0 +1,4 @@ +Partially revert :gh:`105499`. :class:`types.UnionType` no longer an alias +of :data:`typing.Union` and no longer subscriptable, but subscribing +:data:`!typing.Union` returns an instance of :class:`!types.UnionType`. +:data:`!typing.Union` can no longer be used in the ``isinstance()`` checks. diff --git a/Modules/_typingmodule.c b/Modules/_typingmodule.c index e51279c808a2e1..992640c7c22ed1 100644 --- a/Modules/_typingmodule.c +++ b/Modules/_typingmodule.c @@ -7,7 +7,7 @@ #include "Python.h" #include "internal/pycore_interp.h" #include "internal/pycore_typevarobject.h" -#include "internal/pycore_unionobject.h" // _PyUnion_Type +#include "internal/pycore_unionobject.h" // _Py_union_from_tuple #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "clinic/_typingmodule.c.h" @@ -35,8 +35,24 @@ _typing__idfunc(PyObject *module, PyObject *x) } +/*[clinic input] +_typing._make_union + + *args: tuple + +[clinic start generated code]*/ + +static PyObject * +_typing__make_union_impl(PyObject *module, PyObject *args) +/*[clinic end generated code: output=73350430c47d5681 input=5a47e504a2b21ad4]*/ +{ + return _Py_union_from_tuple(args); +} + + static PyMethodDef typing_methods[] = { _TYPING__IDFUNC_METHODDEF + _TYPING__MAKE_UNION_METHODDEF {NULL, NULL, 0, NULL} }; @@ -64,9 +80,6 @@ _typing_exec(PyObject *m) if (PyModule_AddObjectRef(m, "TypeAliasType", (PyObject *)&_PyTypeAlias_Type) < 0) { return -1; } - if (PyModule_AddObjectRef(m, "Union", (PyObject *)&_PyUnion_Type) < 0) { - return -1; - } if (PyModule_AddObjectRef(m, "NoDefault", (PyObject *)&_Py_NoDefaultStruct) < 0) { return -1; } diff --git a/Modules/clinic/_typingmodule.c.h b/Modules/clinic/_typingmodule.c.h index ea415e67153ed8..abff5bfe93ef1e 100644 --- a/Modules/clinic/_typingmodule.c.h +++ b/Modules/clinic/_typingmodule.c.h @@ -2,6 +2,8 @@ preserve [clinic start generated code]*/ +#include "pycore_tuple.h" // _PyTuple_FromArray() + PyDoc_STRVAR(_typing__idfunc__doc__, "_idfunc($module, x, /)\n" "--\n" @@ -9,4 +11,34 @@ PyDoc_STRVAR(_typing__idfunc__doc__, #define _TYPING__IDFUNC_METHODDEF \ {"_idfunc", (PyCFunction)_typing__idfunc, METH_O, _typing__idfunc__doc__}, -/*[clinic end generated code: output=e7ea2a3cb7ab301a input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_typing__make_union__doc__, +"_make_union($module, /, *args)\n" +"--\n" +"\n"); + +#define _TYPING__MAKE_UNION_METHODDEF \ + {"_make_union", _PyCFunction_CAST(_typing__make_union), METH_FASTCALL, _typing__make_union__doc__}, + +static PyObject * +_typing__make_union_impl(PyObject *module, PyObject *args); + +static PyObject * +_typing__make_union(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + __clinic_args = _PyTuple_FromArray(args, nargs); + if (__clinic_args == NULL) { + goto exit; + } + return_value = _typing__make_union_impl(module, __clinic_args); + +exit: + /* Cleanup for args */ + Py_XDECREF(__clinic_args); + + return return_value; +} +/*[clinic end generated code: output=5ad3be515f99ee8a input=a9049054013a1b77]*/ diff --git a/Objects/unionobject.c b/Objects/unionobject.c index 2206ed80ef03fd..862ccd5ef0eb6a 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -372,13 +372,13 @@ union_parameters(PyObject *self, void *Py_UNUSED(unused)) static PyObject * union_name(PyObject *Py_UNUSED(self), void *Py_UNUSED(ignored)) { - return PyUnicode_FromString("Union"); + return PyUnicode_FromString("UnionType"); } static PyObject * union_origin(PyObject *Py_UNUSED(self), void *Py_UNUSED(ignored)) { - return Py_NewRef(&_PyUnion_Type); + return PyImport_ImportModuleAttrString("typing", "Union"); } static PyGetSetDef union_properties[] = { @@ -485,12 +485,6 @@ _Py_union_from_tuple(PyObject *args) return make_union(&ub); } -static PyObject * -union_class_getitem(PyObject *cls, PyObject *args) -{ - return _Py_union_from_tuple(args); -} - static PyObject * union_mro_entries(PyObject *self, PyObject *args) { @@ -500,13 +494,12 @@ union_mro_entries(PyObject *self, PyObject *args) static PyMethodDef union_methods[] = { {"__mro_entries__", union_mro_entries, METH_O}, - {"__class_getitem__", union_class_getitem, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {0} }; PyTypeObject _PyUnion_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) - .tp_name = "typing.Union", + .tp_name = "types.UnionType", .tp_doc = PyDoc_STR("Represent a union type\n" "\n" "E.g. for int | str"),