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"), pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy