diff --git a/Doc/library/importlib.resources.abc.rst b/Doc/library/importlib.resources.abc.rst index 8253a33f591a0b..215bdf14b1b157 100644 --- a/Doc/library/importlib.resources.abc.rst +++ b/Doc/library/importlib.resources.abc.rst @@ -126,8 +126,8 @@ forward slashes (``/``, ``posixpath.sep`` ). For example, the following are equivalent:: - files.joinpath('subdir', 'subsuddir', 'file.txt') - files.joinpath('subdir/subsuddir/file.txt') + files.joinpath('subdir', 'subsubdir', 'file.txt') + files.joinpath('subdir/subsubdir/file.txt') Note that some :class:`!Traversable` implementations might not be updated to the latest version of the protocol. diff --git a/Lib/typing.py b/Lib/typing.py index f1455c273d31ca..98055bc33228fc 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -38,7 +38,6 @@ ParamSpecKwargs, TypeAliasType, Generic, - Union, NoDefault, ) @@ -727,6 +726,72 @@ 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] is equivalent to X | Y and means either X or Y. + + To define a union, use e.g. Union[int, str] or the shorthand int | str. + Using that shorthand is recommended. Details: + + * The arguments must be types and there must be at least one. + + * Unions of unions are flattened, e.g.:: + + Union[Union[int, str], float] == Union[int, str, float] + + * Unions of a single argument vanish, e.g.:: + + Union[int] == int # The constructor actually returns int + + * Redundant arguments are skipped, e.g.:: + + Union[int, str, int] == Union[int, str] == int | str + + * When comparing unions, the argument order is ignored, e.g.:: + + Union[int, str] == Union[str, int] + + * You cannot subclass or instantiate a ``Union``. + + * You cannot write ``Union[X][Y]``. + """ + if not parameters: + raise TypeError("Cannot take a Union of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + + flattened = [] + for p in parameters: + if isinstance(p, types.UnionType): + flattened.extend(p.__args__) + elif hasattr(p, '__origin__') and p.__origin__ is Union: + flattened.extend(p.__args__) + else: + flattened.append(p) + + unique_args = [] + seen = set() + for arg in flattened: + if arg not in seen: + unique_args.append(arg) + seen.add(arg) + + if len(unique_args) == 0: + raise TypeError("Cannot take a Union of no types.") + if len(unique_args) == 1: + return unique_args[0] + + return _UnionGenericAlias(tuple(unique_args)) + + +def _union_from_types(left, right): + """Helper function to create union types avoiding recursion.""" + try: + return left | right + except (TypeError, AttributeError): + return Union[left, right] + + @_SpecialForm def Optional(self, parameters): """Optional[X] is equivalent to Union[X, None].""" @@ -1334,12 +1399,6 @@ def __eq__(self, other): def __hash__(self): return hash((self.__origin__, self.__args__)) - def __or__(self, right): - return Union[self, right] - - def __ror__(self, left): - return Union[left, self] - @_tp_cache def __getitem__(self, args): # Parameterizes an already-parameterized object. @@ -1563,12 +1622,6 @@ def __subclasscheck__(self, cls): def __reduce__(self): return self._name - def __or__(self, right): - return Union[self, right] - - def __ror__(self, left): - return Union[left, self] - class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True): def __repr__(self): @@ -1634,41 +1687,42 @@ def __getitem__(self, params): return self.copy_with(params) -class _UnionGenericAliasMeta(type): - def __instancecheck__(self, inst: object) -> bool: - import warnings - warnings._deprecated("_UnionGenericAlias", remove=(3, 17)) - return isinstance(inst, Union) - - def __subclasscheck__(self, inst: type) -> bool: - import warnings - warnings._deprecated("_UnionGenericAlias", remove=(3, 17)) - return issubclass(inst, Union) - - def __eq__(self, other): - import warnings - warnings._deprecated("_UnionGenericAlias", remove=(3, 17)) - if other is _UnionGenericAlias or other is Union: - return True - return NotImplemented - - def __hash__(self): - return hash(Union) - - -class _UnionGenericAlias(metaclass=_UnionGenericAliasMeta): - """Compatibility hack. +class _SimpleUnion: + """Fallback union representation when types.UnionType creation fails.""" + def __init__(self, args): + self.__args__ = args + self.__origin__ = Union + + def __repr__(self): + return f"Union{self.__args__}" - A class named _UnionGenericAlias used to be used to implement - typing.Union. This class exists to serve as a shim to preserve - the meaning of some code that used to use _UnionGenericAlias - directly. - """ - def __new__(cls, self_cls, parameters, /, *, name=None): - import warnings - warnings._deprecated("_UnionGenericAlias", remove=(3, 17)) - return Union[parameters] +class _UnionGenericAlias: + """A placeholder class for union types that wraps types.UnionType functionality.""" + + def __new__(cls, args): + if len(args) == 1: + return args[0] + elif len(args) == 2: + try: + result = args[0] | args[1] + if hasattr(result, '__class__') and result.__class__.__name__ == 'UnionType': + return result + else: + return _SimpleUnion(args) + except (TypeError, AttributeError): + return _SimpleUnion(args) + else: + try: + result = args[0] + for arg in args[1:]: + result = result | arg + if hasattr(result, '__class__') and result.__class__.__name__ == 'UnionType': + return result + else: + return _SimpleUnion(args) + except (TypeError, AttributeError): + return _SimpleUnion(args) def _value_and_type_iter(parameters): @@ -3463,7 +3517,7 @@ def writelines(self, lines: list[AnyStr]) -> None: pass @abstractmethod - def __enter__(self) -> IO[AnyStr]: + def __enter__(self) -> 'IO[AnyStr]': pass @abstractmethod @@ -3481,7 +3535,7 @@ def write(self, s: bytes | bytearray) -> int: pass @abstractmethod - def __enter__(self) -> BinaryIO: + def __enter__(self) -> 'BinaryIO': pass @@ -3516,7 +3570,7 @@ def newlines(self) -> Any: pass @abstractmethod - def __enter__(self) -> TextIO: + def __enter__(self) -> 'TextIO': pass @@ -3550,7 +3604,7 @@ def dataclass_transform( order_default: bool = False, kw_only_default: bool = False, frozen_default: bool = False, - field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = (), + field_specifiers: tuple[Union[type[Any], Callable[..., Any]], ...] = (), **kwargs: Any, ) -> _IdentityCallable: """Decorator to mark an object as providing dataclass-like behaviour. diff --git a/Modules/_typingmodule.c b/Modules/_typingmodule.c index e51279c808a2e1..03bda7085dd527 100644 --- a/Modules/_typingmodule.c +++ b/Modules/_typingmodule.c @@ -64,9 +64,7 @@ _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; }