diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 8253d330b95b811..874b985d2ff8b3d 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -260,7 +260,7 @@ def clear_caches(): except KeyError: pass else: - for f in typing._cleanups: + for f in typing._CACHE_CLEANUPS: f() try: diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index fbca198aab5180f..361077e47e0833a 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -24,7 +24,7 @@ class Example: class Forward: ... def clear_typing_caches(): - for f in typing._cleanups: + for f in typing._CACHE_CLEANUPS: f() diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f10b0aea3cd7b9c..7ac02b2db62f9d3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -71,7 +71,7 @@ def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): raise self.failureException(message) def clear_caches(self): - for f in typing._cleanups: + for f in typing._CACHE_CLEANUPS: f() diff --git a/Lib/typing.py b/Lib/typing.py index 434574559e04fcb..12c6a49edd7ae20 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -256,8 +256,8 @@ def _type_repr(obj): return repr(obj) -def _collect_type_parameters(args, *, enforce_default_ordering: bool = True): - """Collect all type parameters in args +def _collect_type_parameters(params, *, enforce_default_ordering: bool = True): + """Collect all type parameters in params in order of first appearance (lexicographic order). For example:: @@ -272,37 +272,39 @@ def _collect_type_parameters(args, *, enforce_default_ordering: bool = True): # or after TypeVarTuple type_var_tuple_encountered = False parameters = [] - for t in args: - if isinstance(t, type): + for param in params: + if isinstance(param, type): # We don't want __parameters__ descriptor of a bare Python class. pass - elif isinstance(t, tuple): + elif isinstance(param, tuple): # `t` might be a tuple, when `ParamSpec` is substituted with # `[T, int]`, or `[int, *Ts]`, etc. - for x in t: - for collected in _collect_type_parameters([x]): + for elem in param: + for collected in _collect_type_parameters([elem]): if collected not in parameters: parameters.append(collected) - elif hasattr(t, '__typing_subst__'): - if t not in parameters: + elif hasattr(param, '__typing_subst__'): + if param not in parameters: if enforce_default_ordering: - if type_var_tuple_encountered and t.has_default(): + if type_var_tuple_encountered and param.has_default(): raise TypeError('Type parameter with a default' ' follows TypeVarTuple') - if t.has_default(): + elif param.has_default(): default_encountered = True elif default_encountered: - raise TypeError(f'Type parameter {t!r} without a default' - ' follows type parameter with a default') + raise TypeError( + f'Type parameter {param!r} without a default' + ' follows type parameter with a default' + ) - parameters.append(t) + parameters.append(param) else: - if _is_unpacked_typevartuple(t): + if _is_unpacked_typevartuple(param): type_var_tuple_encountered = True - for x in getattr(t, '__parameters__', ()): - if x not in parameters: - parameters.append(x) + for elem in getattr(param, '__parameters__', ()): + if elem not in parameters: + parameters.append(elem) return tuple(parameters) @@ -333,20 +335,25 @@ def _check_generic_specialization(cls, arguments): else: expect_val = expected_len - raise TypeError(f"Too {'many' if actual_len > expected_len else 'few'} arguments" - f" for {cls}; actual {actual_len}, expected {expect_val}") + raise TypeError( + f"Too {'many' if actual_len > expected_len else 'few'} arguments" + f" for {cls}; actual {actual_len}, expected {expect_val}" + ) def _unpack_args(*args): newargs = [] for arg in args: - subargs = getattr(arg, '__typing_unpacked_tuple_args__', None) - if subargs is not None and not (subargs and subargs[-1] is ...): - newargs.extend(subargs) - else: - newargs.append(arg) + match getattr(arg, '__typing_unpacked_tuple_args__', None): + case None | [*_, types.EllipsisType()]: + newargs.append(arg) + case list() as subargs: + newargs.extend(subargs) + case _ as unreachable: + assert_never(unreachable) return newargs + def _deduplicate(params, *, unhashable_fallback=False): # Weed out strict duplicates, preserving the first of each occurrence. try: @@ -357,13 +364,15 @@ def _deduplicate(params, *, unhashable_fallback=False): # Happens for cases like `Annotated[dict, {'x': IntValidator()}]` return _deduplicate_unhashable(params) + def _deduplicate_unhashable(unhashable_params): new_unhashable = [] - for t in unhashable_params: - if t not in new_unhashable: - new_unhashable.append(t) + for param in unhashable_params: + if param not in new_unhashable: + new_unhashable.append(param) return new_unhashable + def _compare_args_orderless(first_args, second_args): first_unhashable = _deduplicate_unhashable(first_args) second_unhashable = _deduplicate_unhashable(second_args) @@ -375,6 +384,7 @@ def _compare_args_orderless(first_args, second_args): return False return not t + def _remove_dups_flatten(parameters): """Internal helper for Union creation and substitution. @@ -382,11 +392,11 @@ def _remove_dups_flatten(parameters): """ # Flatten out Union[Union[...], ...]. params = [] - for p in parameters: - if isinstance(p, (_UnionGenericAlias, types.UnionType)): - params.extend(p.__args__) + for param in parameters: + if isinstance(param, (_UnionGenericAlias, types.UnionType)): + params.extend(param.__args__) else: - params.append(p) + params.append(param) return tuple(_deduplicate(params, unhashable_fallback=True)) @@ -394,16 +404,16 @@ def _remove_dups_flatten(parameters): def _flatten_literal_params(parameters): """Internal helper for Literal creation: flatten Literals among parameters.""" params = [] - for p in parameters: - if isinstance(p, _LiteralGenericAlias): - params.extend(p.__args__) + for param in parameters: + if isinstance(param, _LiteralGenericAlias): + params.extend(param.__args__) else: - params.append(p) + params.append(param) return tuple(params) -_cleanups = [] -_caches = {} +_CACHES = {} +_CACHE_CLEANUPS = [] def _tp_cache(func=None, /, *, typed=False): @@ -413,19 +423,19 @@ def _tp_cache(func=None, /, *, typed=False): """ def decorator(func): # The callback 'inner' references the newly created lru_cache - # indirectly by performing a lookup in the global '_caches' dictionary. + # indirectly by performing a lookup in the global '_CACHES' dictionary. # This breaks a reference that can be problematic when combined with # C API extensions that leak references to types. See GH-98253. cache = functools.lru_cache(typed=typed)(func) - _caches[func] = cache - _cleanups.append(cache.cache_clear) + _CACHES[func] = cache + _CACHE_CLEANUPS.append(cache.cache_clear) del cache @functools.wraps(func) def inner(*args, **kwds): try: - return _caches[func](*args, **kwds) + return _CACHES[func](*args, **kwds) except TypeError: pass # All real errors (not unhashable args) are raised below. return func(*args, **kwds) @@ -459,7 +469,9 @@ def __repr__(self): _sentinel = _Sentinel() -def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset()): +def _eval_type( + typ, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset() + ): """Evaluate all forward references in the given type t. For use of globalns and localns see the docstring for get_type_hints(). @@ -469,37 +481,39 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f if type_params is _sentinel: _deprecation_warning_for_no_type_params_passed("typing._eval_type") type_params = () - if isinstance(t, ForwardRef): - return t._evaluate(globalns, localns, type_params, recursive_guard=recursive_guard) - if isinstance(t, (_GenericAlias, GenericAlias, types.UnionType)): - if isinstance(t, GenericAlias): + if isinstance(typ, ForwardRef): + return typ._evaluate( + globalns, localns, type_params, recursive_guard=recursive_guard + ) + if isinstance(typ, (_GenericAlias, GenericAlias, types.UnionType)): + if isinstance(typ, GenericAlias): args = tuple( ForwardRef(arg) if isinstance(arg, str) else arg - for arg in t.__args__ + for arg in typ.__args__ ) - is_unpacked = t.__unpacked__ - if _should_unflatten_callable_args(t, args): - t = t.__origin__[(args[:-1], args[-1])] + is_unpacked = typ.__unpacked__ + if _should_unflatten_callable_args(typ, args): + typ = typ.__origin__[(args[:-1], args[-1])] else: - t = t.__origin__[args] + typ = typ.__origin__[args] if is_unpacked: - t = Unpack[t] + typ = Unpack[typ] - ev_args = tuple( + evaluated_args = tuple( _eval_type( - a, globalns, localns, type_params, recursive_guard=recursive_guard + arg, globalns, localns, type_params, recursive_guard=recursive_guard ) - for a in t.__args__ + for arg in typ.__args__ ) - if ev_args == t.__args__: - return t - if isinstance(t, GenericAlias): - return GenericAlias(t.__origin__, ev_args) - if isinstance(t, types.UnionType): - return functools.reduce(operator.or_, ev_args) + if evaluated_args == typ.__args__: + return typ + if isinstance(typ, GenericAlias): + return GenericAlias(typ.__origin__, evaluated_args) + if isinstance(typ, types.UnionType): + return functools.reduce(operator.or_, evaluated_args) else: - return t.copy_with(ev_args) - return t + return typ.copy_with(evaluated_args) + return typ class _Final: @@ -507,8 +521,8 @@ class _Final: __slots__ = ('__weakref__',) - def __init_subclass__(cls, /, *args, **kwds): - if '_root' not in kwds: + def __init_subclass__(cls, /, *args, **kwargs): + if '_root' not in kwargs: raise TypeError("Cannot subclass special typing classes") @@ -533,12 +547,12 @@ def __iter__(self): raise TypeError() # Internal indicator of special typing constructs. # See __doc__ instance attribute for specific docs. class _SpecialForm(_Final, _NotIterable, _root=True): - __slots__ = ('_name', '__doc__', '_getitem') + __slots__ = ('_name', '__doc__', '_getitem_callback') - def __init__(self, getitem): - self._getitem = getitem - self._name = getitem.__name__ - self.__doc__ = getitem.__doc__ + def __init__(self, getitem_callback): + self._getitem_callback = getitem_callback + self._name = getitem_callback.__name__ + self.__doc__ = getitem_callback.__doc__ def __getattr__(self, item): if item in {'__name__', '__qualname__'}: @@ -572,14 +586,14 @@ def __subclasscheck__(self, cls): @_tp_cache def __getitem__(self, parameters): - return self._getitem(self, parameters) + return self._getitem_callback(self, parameters) class _TypedCacheSpecialForm(_SpecialForm, _root=True): def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) - return self._getitem(self, *parameters) + return self._getitem_callback(self, *parameters) class _AnyMeta(type): @@ -630,6 +644,7 @@ def stop() -> NoReturn: """ raise TypeError(f"{self} is not subscriptable") + # This is semantically identical to NoReturn, but it is implemented # separately so that type checkers can distinguish between the two # if they want. @@ -727,6 +742,7 @@ class Starship: item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True) return _GenericAlias(self, (item,)) + @_SpecialForm def Final(self, parameters): """Special typing construct to indicate final names to type checkers. @@ -749,6 +765,7 @@ 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. @@ -785,7 +802,7 @@ def Union(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) msg = "Union[arg, ...]: each arg must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) + parameters = tuple(_type_check(param, msg) for param in parameters) parameters = _remove_dups_flatten(parameters) if len(parameters) == 1: return parameters[0] @@ -793,6 +810,7 @@ def Union(self, parameters): return _UnionGenericAlias(self, parameters, name="Optional") return _UnionGenericAlias(self, parameters) + def _make_union(left, right): """Used from the C implementation of TypeVar. @@ -802,12 +820,14 @@ def _make_union(left, right): """ return Union[left, right] + @_SpecialForm def Optional(self, parameters): """Optional[X] is equivalent to Union[X, None].""" arg = _type_check(parameters, f"{self} requires a single type.") return Union[arg, type(None)] + @_TypedCacheSpecialForm @_tp_cache(typed=True) def Literal(self, *parameters): @@ -836,7 +856,9 @@ def open_helper(file: str, mode: MODE) -> str: parameters = _flatten_literal_params(parameters) try: - parameters = tuple(p for p, _ in _deduplicate(list(_value_and_type_iter(parameters)))) + parameters = tuple( + param for param, _ in _deduplicate(list(_value_and_type_iter(parameters))) + ) except TypeError: # unhashable parameters pass @@ -878,11 +900,14 @@ def Concatenate(self, parameters): raise TypeError("Cannot take a Concatenate of no types.") if not isinstance(parameters, tuple): parameters = (parameters,) - if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)): + if not isinstance(parameters[-1], (ParamSpec, types.EllipsisType)): raise TypeError("The last parameter to Concatenate should be a " "ParamSpec variable or ellipsis.") msg = "Concatenate[arg, ...]: each arg must be a type." - parameters = (*(_type_check(p, msg) for p in parameters[:-1]), parameters[-1]) + parameters = ( + *(_type_check(param, msg) for param in parameters[:-1]), + parameters[-1] + ) return _ConcatenateGenericAlias(self, parameters) @@ -1134,10 +1159,10 @@ def _typevartuple_prepare_subst(self, alias, args): if isinstance(param, TypeVarTuple): raise TypeError(f"More than one TypeVarTuple parameter in {alias}") - alen = len(args) - plen = len(params) + num_args = len(args) + num_params = len(params) left = typevartuple_index - right = plen - typevartuple_index - 1 + right = num_params - typevartuple_index - 1 var_tuple_index = None fillarg = None for k, arg in enumerate(args): @@ -1150,21 +1175,23 @@ def _typevartuple_prepare_subst(self, alias, args): fillarg = subargs[0] if var_tuple_index is not None: left = min(left, var_tuple_index) - right = min(right, alen - var_tuple_index - 1) - elif left + right > alen: - raise TypeError(f"Too few arguments for {alias};" - f" actual {alen}, expected at least {plen-1}") - if left == alen - right and self.has_default(): + right = min(right, num_args - var_tuple_index - 1) + elif left + right > num_args: + raise TypeError( + f"Too few arguments for {alias};" + f" actual {num_args}, expected at least {num_params-1}" + ) + if left == num_args - right and self.has_default(): replacement = _unpack_args(self.__default__) else: - replacement = args[left: alen - right] + replacement = args[left: num_args - right] return ( *args[:left], - *([fillarg]*(typevartuple_index - left)), + *([fillarg] * (typevartuple_index - left)), replacement, - *([fillarg]*(plen - right - left - typevartuple_index - 1)), - *args[alen - right:], + *([fillarg] * (num_params - right - left - typevartuple_index - 1)), + *args[num_args - right:], ) @@ -1208,7 +1235,7 @@ def _generic_class_getitem(cls, args): if not isinstance(args, tuple): args = (args,) - args = tuple(_type_convert(p) for p in args) + args = tuple(_type_convert(arg) for arg in args) is_generic_or_protocol = cls in (Generic, Protocol) if is_generic_or_protocol: @@ -1284,6 +1311,7 @@ def _generic_init_subclass(cls, *args, **kwargs): def _is_dunder(attr): return attr.startswith('__') and attr.endswith('__') + class _BaseGenericAlias(_Final, _root=True): """The central part of the internal API. @@ -1325,21 +1353,21 @@ def __mro_entries__(self, bases): # appear exactly once in the final bases tuple. If we let it appear # multiple times, we risk "can't form a consistent MRO" errors. i = bases.index(self) - for b in bases[i+1:]: - if isinstance(b, _BaseGenericAlias): + for base in bases[i+1:]: + if isinstance(base, _BaseGenericAlias): break - if not isinstance(b, type): - meth = getattr(b, "__mro_entries__", None) + if not isinstance(base, type): + meth = getattr(base, "__mro_entries__", None) new_bases = meth(bases) if meth else None if ( - isinstance(new_bases, tuple) and - any( - isinstance(b2, type) and issubclass(b2, Generic) - for b2 in new_bases + isinstance(new_bases, tuple) + and any( + isinstance(new_base, type) and issubclass(new_base, Generic) + for new_base in new_bases ) ): break - elif issubclass(b, Generic): + elif issubclass(base, Generic): break else: res.append(Generic) @@ -1414,8 +1442,10 @@ def __init__(self, origin, args, *, inst=True, name=None): super().__init__(origin, inst=inst, name=name) if not isinstance(args, tuple): args = (args,) - self.__args__ = tuple(... if a is _TypingEllipsis else - a for a in args) + self.__args__ = tuple( + ... if arg is _TypingEllipsis else arg + for arg in args + ) enforce_default_ordering = origin in (Generic, Protocol) self.__parameters__ = _collect_type_parameters( args, @@ -1465,10 +1495,9 @@ def __getitem__(self, args): # Preprocess `args`. if not isinstance(args, tuple): args = (args,) - args = _unpack_args(*(_type_convert(p) for p in args)) + args = _unpack_args(*(_type_convert(arg) for arg in args)) new_args = self._determine_new_args(args) - r = self.copy_with(new_args) - return r + return self.copy_with(new_args) def _determine_new_args(self, args): # Determines new __args__ for __getitem__. @@ -1491,11 +1520,14 @@ def _determine_new_args(self, args): prepare = getattr(param, '__typing_prepare_subst__', None) if prepare is not None: args = prepare(self, args) - alen = len(args) - plen = len(params) - if alen != plen: - raise TypeError(f"Too {'many' if alen > plen else 'few'} arguments for {self};" - f" actual {alen}, expected {plen}") + num_args = len(args) + num_params = len(params) + if num_args != num_params: + raise TypeError( + f"Too {'many' if num_args > num_params else 'few'}" + f" arguments for {self};" + f" actual {num_args}, expected {num_params}" + ) new_arg_by_param = dict(zip(params, args)) return tuple(self._make_substitution(self.__args__, new_arg_by_param)) @@ -1978,7 +2010,7 @@ def _lazy_load_getattr_static(): return getattr_static -_cleanups.append(_lazy_load_getattr_static.cache_clear) +_CACHE_CLEANUPS.append(_lazy_load_getattr_static.cache_clear) def _pickle_psargs(psargs): return ParamSpecArgs, (psargs.__origin__,) @@ -3764,23 +3796,27 @@ def __getattr__(attr): Soft-deprecated objects which are costly to create are only created on-demand here. """ - if attr in {"Pattern", "Match"}: - import re - obj = _alias(getattr(re, attr), 1) - elif attr in {"ContextManager", "AsyncContextManager"}: - import contextlib - obj = _alias(getattr(contextlib, f"Abstract{attr}"), 2, name=attr, defaults=(bool | None,)) - elif attr == "_collect_parameters": - import warnings + match attr: + case "Pattern" | "Match": + import re + obj = _alias(getattr(re, attr), 1) + + case "ContextManager" | "AsyncContextManager": + import contextlib + obj = _alias(getattr(contextlib, f"Abstract{attr}"), 2, name=attr, defaults=(bool | None,)) + + case "_collect_parameters": + import warnings + depr_message = ( + "The private _collect_parameters function is deprecated and will be" + " removed in a future version of Python. Any use of private functions" + " is discouraged and may break in the future." + ) + warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2) + obj = _collect_type_parameters + + case _: + raise AttributeError(f"module {__name__!r} has no attribute {attr!r}") - depr_message = ( - "The private _collect_parameters function is deprecated and will be" - " removed in a future version of Python. Any use of private functions" - " is discouraged and may break in the future." - ) - warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2) - obj = _collect_type_parameters - else: - raise AttributeError(f"module {__name__!r} has no attribute {attr!r}") globals()[attr] = obj return obj