Skip to content

Commit 78e8785

Browse files
committed
Refactor the typing module to remove C extension support for Union.
1 parent 03017a8 commit 78e8785

File tree

2 files changed

+114
-53
lines changed

2 files changed

+114
-53
lines changed

Lib/typing.py

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
ParamSpecKwargs,
3939
TypeAliasType,
4040
Generic,
41-
Union,
4241
NoDefault,
4342
)
4443

@@ -727,6 +726,81 @@ class FastConnector(Connection):
727726
item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True)
728727
return _GenericAlias(self, (item,))
729728

729+
@_SpecialForm
730+
def Union(self, parameters):
731+
"""Union type; Union[X, Y] is equivalent to X | Y and means either X or Y.
732+
733+
To define a union, use e.g. Union[int, str] or the shorthand int | str.
734+
Using that shorthand is recommended. Details:
735+
736+
* The arguments must be types and there must be at least one.
737+
738+
* Unions of unions are flattened, e.g.::
739+
740+
Union[Union[int, str], float] == Union[int, str, float]
741+
742+
* Unions of a single argument vanish, e.g.::
743+
744+
Union[int] == int # The constructor actually returns int
745+
746+
* Redundant arguments are skipped, e.g.::
747+
748+
Union[int, str, int] == Union[int, str] == int | str
749+
750+
* When comparing unions, the argument order is ignored, e.g.::
751+
752+
Union[int, str] == Union[str, int]
753+
754+
* You cannot subclass or instantiate a ``Union``.
755+
756+
* You cannot write ``Union[X][Y]``.
757+
"""
758+
if not parameters:
759+
raise TypeError("Cannot take a Union of no types.")
760+
if not isinstance(parameters, tuple):
761+
parameters = (parameters,)
762+
763+
flattened = []
764+
for p in parameters:
765+
if isinstance(p, types.UnionType):
766+
flattened.extend(p.__args__)
767+
elif hasattr(p, '__origin__') and p.__origin__ is Union:
768+
flattened.extend(p.__args__)
769+
else:
770+
flattened.append(p)
771+
772+
unique_args = []
773+
seen = set()
774+
for arg in flattened:
775+
if arg not in seen:
776+
unique_args.append(arg)
777+
seen.add(arg)
778+
779+
if len(unique_args) == 0:
780+
raise TypeError("Cannot take a Union of no types.")
781+
if len(unique_args) == 1:
782+
return unique_args[0]
783+
784+
return _UnionGenericAlias(tuple(unique_args))
785+
786+
787+
def _union_from_types(left, right):
788+
"""Helper function to create union types avoiding recursion."""
789+
try:
790+
if hasattr(left, '__or__') and not isinstance(left, _GenericAlias):
791+
return left | right
792+
elif hasattr(right, '__ror__') and not isinstance(right, _GenericAlias):
793+
return right.__ror__(left)
794+
else:
795+
if hasattr(left, '__origin__'):
796+
left = left.__origin__
797+
if hasattr(right, '__origin__'):
798+
right = right.__origin__
799+
return left | right
800+
except (TypeError, AttributeError):
801+
return f"Union[{left}, {right}]"
802+
803+
730804
@_SpecialForm
731805
def Optional(self, parameters):
732806
"""Optional[X] is equivalent to Union[X, None]."""
@@ -1334,12 +1408,6 @@ def __eq__(self, other):
13341408
def __hash__(self):
13351409
return hash((self.__origin__, self.__args__))
13361410

1337-
def __or__(self, right):
1338-
return Union[self, right]
1339-
1340-
def __ror__(self, left):
1341-
return Union[left, self]
1342-
13431411
@_tp_cache
13441412
def __getitem__(self, args):
13451413
# Parameterizes an already-parameterized object.
@@ -1563,12 +1631,6 @@ def __subclasscheck__(self, cls):
15631631
def __reduce__(self):
15641632
return self._name
15651633

1566-
def __or__(self, right):
1567-
return Union[self, right]
1568-
1569-
def __ror__(self, left):
1570-
return Union[left, self]
1571-
15721634

15731635
class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True):
15741636
def __repr__(self):
@@ -1634,41 +1696,42 @@ def __getitem__(self, params):
16341696
return self.copy_with(params)
16351697

16361698

1637-
class _UnionGenericAliasMeta(type):
1638-
def __instancecheck__(self, inst: object) -> bool:
1639-
import warnings
1640-
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
1641-
return isinstance(inst, Union)
1642-
1643-
def __subclasscheck__(self, inst: type) -> bool:
1644-
import warnings
1645-
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
1646-
return issubclass(inst, Union)
1647-
1648-
def __eq__(self, other):
1649-
import warnings
1650-
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
1651-
if other is _UnionGenericAlias or other is Union:
1652-
return True
1653-
return NotImplemented
1654-
1655-
def __hash__(self):
1656-
return hash(Union)
1657-
1658-
1659-
class _UnionGenericAlias(metaclass=_UnionGenericAliasMeta):
1660-
"""Compatibility hack.
1699+
class _SimpleUnion:
1700+
"""Fallback union representation when types.UnionType creation fails."""
1701+
def __init__(self, args):
1702+
self.__args__ = args
1703+
self.__origin__ = Union
1704+
1705+
def __repr__(self):
1706+
return f"Union{self.__args__}"
16611707

1662-
A class named _UnionGenericAlias used to be used to implement
1663-
typing.Union. This class exists to serve as a shim to preserve
1664-
the meaning of some code that used to use _UnionGenericAlias
1665-
directly.
16661708

1667-
"""
1668-
def __new__(cls, self_cls, parameters, /, *, name=None):
1669-
import warnings
1670-
warnings._deprecated("_UnionGenericAlias", remove=(3, 17))
1671-
return Union[parameters]
1709+
class _UnionGenericAlias:
1710+
"""A placeholder class for union types that wraps types.UnionType functionality."""
1711+
1712+
def __new__(cls, args):
1713+
if len(args) == 1:
1714+
return args[0]
1715+
elif len(args) == 2:
1716+
try:
1717+
result = args[0] | args[1]
1718+
if hasattr(result, '__class__') and result.__class__.__name__ == 'UnionType':
1719+
return result
1720+
else:
1721+
return _SimpleUnion(args)
1722+
except (TypeError, AttributeError):
1723+
return _SimpleUnion(args)
1724+
else:
1725+
try:
1726+
result = args[0]
1727+
for arg in args[1:]:
1728+
result = result | arg
1729+
if hasattr(result, '__class__') and result.__class__.__name__ == 'UnionType':
1730+
return result
1731+
else:
1732+
return _SimpleUnion(args)
1733+
except (TypeError, AttributeError):
1734+
return _SimpleUnion(args)
16721735

16731736

16741737
def _value_and_type_iter(parameters):
@@ -3463,7 +3526,7 @@ def writelines(self, lines: list[AnyStr]) -> None:
34633526
pass
34643527

34653528
@abstractmethod
3466-
def __enter__(self) -> IO[AnyStr]:
3529+
def __enter__(self) -> 'IO[AnyStr]':
34673530
pass
34683531

34693532
@abstractmethod
@@ -3481,7 +3544,7 @@ def write(self, s: bytes | bytearray) -> int:
34813544
pass
34823545

34833546
@abstractmethod
3484-
def __enter__(self) -> BinaryIO:
3547+
def __enter__(self) -> 'BinaryIO':
34853548
pass
34863549

34873550

@@ -3516,7 +3579,7 @@ def newlines(self) -> Any:
35163579
pass
35173580

35183581
@abstractmethod
3519-
def __enter__(self) -> TextIO:
3582+
def __enter__(self) -> 'TextIO':
35203583
pass
35213584

35223585

@@ -3550,7 +3613,7 @@ def dataclass_transform(
35503613
order_default: bool = False,
35513614
kw_only_default: bool = False,
35523615
frozen_default: bool = False,
3553-
field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = (),
3616+
field_specifiers: tuple[Union[type[Any], Callable[..., Any]], ...] = (),
35543617
**kwargs: Any,
35553618
) -> _IdentityCallable:
35563619
"""Decorator to mark an object as providing dataclass-like behaviour.

Modules/_typingmodule.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ _typing_exec(PyObject *m)
6464
if (PyModule_AddObjectRef(m, "TypeAliasType", (PyObject *)&_PyTypeAlias_Type) < 0) {
6565
return -1;
6666
}
67-
if (PyModule_AddObjectRef(m, "Union", (PyObject *)&_PyUnion_Type) < 0) {
68-
return -1;
69-
}
67+
7068
if (PyModule_AddObjectRef(m, "NoDefault", (PyObject *)&_Py_NoDefaultStruct) < 0) {
7169
return -1;
7270
}

0 commit comments

Comments
 (0)