From cc5aaf2cc744dfbc0a16c1d20607853874307333 Mon Sep 17 00:00:00 2001 From: BvB93 <43369155+BvB93@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:51:48 +0100 Subject: [PATCH] MAINT: Remove `npt._GenericAlias` in favor of py39 `types.GenericAlias` --- numpy/_typing/__init__.py | 6 +- numpy/_typing/_add_docstring.py | 2 +- numpy/_typing/_array_like.py | 3 + numpy/_typing/_callable.pyi | 2 +- numpy/_typing/_generic_alias.py | 245 ----------------------- numpy/typing/tests/test_generic_alias.py | 188 ----------------- 6 files changed, 6 insertions(+), 440 deletions(-) delete mode 100644 numpy/_typing/_generic_alias.py delete mode 100644 numpy/typing/tests/test_generic_alias.py diff --git a/numpy/_typing/__init__.py b/numpy/_typing/__init__.py index a4e763050cd2..29922d958efb 100644 --- a/numpy/_typing/__init__.py +++ b/numpy/_typing/__init__.py @@ -180,6 +180,7 @@ class _8Bit(_16Bit): # type: ignore[misc] _DTypeLikeComplex_co as _DTypeLikeComplex_co, ) from ._array_like import ( + NDArray as NDArray, ArrayLike as ArrayLike, _ArrayLike as _ArrayLike, _FiniteNestedSequence as _FiniteNestedSequence, @@ -201,11 +202,6 @@ class _8Bit(_16Bit): # type: ignore[misc] _ArrayLikeUnknown as _ArrayLikeUnknown, _UnknownType as _UnknownType, ) -from ._generic_alias import ( - NDArray as NDArray, - _DType as _DType, - _GenericAlias as _GenericAlias, -) if TYPE_CHECKING: from ._ufunc import ( diff --git a/numpy/_typing/_add_docstring.py b/numpy/_typing/_add_docstring.py index 10d77f5161d3..f84d19271c23 100644 --- a/numpy/_typing/_add_docstring.py +++ b/numpy/_typing/_add_docstring.py @@ -3,7 +3,7 @@ import re import textwrap -from ._generic_alias import NDArray +from ._array_like import NDArray _docstrings_list = [] diff --git a/numpy/_typing/_array_like.py b/numpy/_typing/_array_like.py index 6dbe86c6862b..8ac91172d05d 100644 --- a/numpy/_typing/_array_like.py +++ b/numpy/_typing/_array_like.py @@ -23,9 +23,12 @@ _T = TypeVar("_T") _ScalarType = TypeVar("_ScalarType", bound=generic) +_ScalarType_co = TypeVar("_ScalarType_co", bound=generic, covariant=True) _DType = TypeVar("_DType", bound=dtype[Any]) _DType_co = TypeVar("_DType_co", covariant=True, bound=dtype[Any]) +NDArray = ndarray[Any, dtype[_ScalarType_co]] + # The `_SupportsArray` protocol only cares about the default dtype # (i.e. `dtype=None` or no `dtype` parameter at all) of the to-be returned # array. diff --git a/numpy/_typing/_callable.pyi b/numpy/_typing/_callable.pyi index 5dfe9e7c2342..ee818e90575b 100644 --- a/numpy/_typing/_callable.pyi +++ b/numpy/_typing/_callable.pyi @@ -43,7 +43,7 @@ from ._scalars import ( _NumberLike_co, ) from . import NBitBase -from ._generic_alias import NDArray +from ._array_like import NDArray from ._nested_sequence import _NestedSequence _T1 = TypeVar("_T1") diff --git a/numpy/_typing/_generic_alias.py b/numpy/_typing/_generic_alias.py deleted file mode 100644 index 01cd224adbe9..000000000000 --- a/numpy/_typing/_generic_alias.py +++ /dev/null @@ -1,245 +0,0 @@ -from __future__ import annotations - -import sys -import types -from collections.abc import Generator, Iterable, Iterator -from typing import ( - Any, - ClassVar, - NoReturn, - TypeVar, - TYPE_CHECKING, -) - -import numpy as np - -__all__ = ["_GenericAlias", "NDArray"] - -_T = TypeVar("_T", bound="_GenericAlias") - - -def _to_str(obj: object) -> str: - """Helper function for `_GenericAlias.__repr__`.""" - if obj is Ellipsis: - return '...' - elif isinstance(obj, type) and not isinstance(obj, _GENERIC_ALIAS_TYPE): - if obj.__module__ == 'builtins': - return obj.__qualname__ - else: - return f'{obj.__module__}.{obj.__qualname__}' - else: - return repr(obj) - - -def _parse_parameters(args: Iterable[Any]) -> Generator[TypeVar, None, None]: - """Search for all typevars and typevar-containing objects in `args`. - - Helper function for `_GenericAlias.__init__`. - - """ - for i in args: - if hasattr(i, "__parameters__"): - yield from i.__parameters__ - elif isinstance(i, TypeVar): - yield i - - -def _reconstruct_alias(alias: _T, parameters: Iterator[TypeVar]) -> _T: - """Recursively replace all typevars with those from `parameters`. - - Helper function for `_GenericAlias.__getitem__`. - - """ - args = [] - for i in alias.__args__: - if isinstance(i, TypeVar): - value: Any = next(parameters) - elif isinstance(i, _GenericAlias): - value = _reconstruct_alias(i, parameters) - elif hasattr(i, "__parameters__"): - prm_tup = tuple(next(parameters) for _ in i.__parameters__) - value = i[prm_tup] - else: - value = i - args.append(value) - - cls = type(alias) - return cls(alias.__origin__, tuple(args), alias.__unpacked__) - - -class _GenericAlias: - """A python-based backport of the `types.GenericAlias` class. - - E.g. for ``t = list[int]``, ``t.__origin__`` is ``list`` and - ``t.__args__`` is ``(int,)``. - - See Also - -------- - :pep:`585` - The PEP responsible for introducing `types.GenericAlias`. - - """ - - __slots__ = ( - "__weakref__", - "_origin", - "_args", - "_parameters", - "_hash", - "_starred", - ) - - @property - def __origin__(self) -> type: - return super().__getattribute__("_origin") - - @property - def __args__(self) -> tuple[object, ...]: - return super().__getattribute__("_args") - - @property - def __parameters__(self) -> tuple[TypeVar, ...]: - """Type variables in the ``GenericAlias``.""" - return super().__getattribute__("_parameters") - - @property - def __unpacked__(self) -> bool: - return super().__getattribute__("_starred") - - @property - def __typing_unpacked_tuple_args__(self) -> tuple[object, ...] | None: - # NOTE: This should return `__args__` if `__origin__` is a tuple, - # which should never be the case with how `_GenericAlias` is used - # within numpy - return None - - def __init__( - self, - origin: type, - args: object | tuple[object, ...], - starred: bool = False, - ) -> None: - self._origin = origin - self._args = args if isinstance(args, tuple) else (args,) - self._parameters = tuple(_parse_parameters(self.__args__)) - self._starred = starred - - @property - def __call__(self) -> type[Any]: - return self.__origin__ - - def __reduce__(self: _T) -> tuple[ - type[_T], - tuple[type[Any], tuple[object, ...], bool], - ]: - cls = type(self) - return cls, (self.__origin__, self.__args__, self.__unpacked__) - - def __mro_entries__(self, bases: Iterable[object]) -> tuple[type[Any]]: - return (self.__origin__,) - - def __dir__(self) -> list[str]: - """Implement ``dir(self)``.""" - cls = type(self) - dir_origin = set(dir(self.__origin__)) - return sorted(cls._ATTR_EXCEPTIONS | dir_origin) - - def __hash__(self) -> int: - """Return ``hash(self)``.""" - # Attempt to use the cached hash - try: - return super().__getattribute__("_hash") - except AttributeError: - self._hash: int = ( - hash(self.__origin__) ^ - hash(self.__args__) ^ - hash(self.__unpacked__) - ) - return super().__getattribute__("_hash") - - def __instancecheck__(self, obj: object) -> NoReturn: - """Check if an `obj` is an instance.""" - raise TypeError("isinstance() argument 2 cannot be a " - "parameterized generic") - - def __subclasscheck__(self, cls: type) -> NoReturn: - """Check if a `cls` is a subclass.""" - raise TypeError("issubclass() argument 2 cannot be a " - "parameterized generic") - - def __repr__(self) -> str: - """Return ``repr(self)``.""" - args = ", ".join(_to_str(i) for i in self.__args__) - origin = _to_str(self.__origin__) - prefix = "*" if self.__unpacked__ else "" - return f"{prefix}{origin}[{args}]" - - def __getitem__(self: _T, key: object | tuple[object, ...]) -> _T: - """Return ``self[key]``.""" - key_tup = key if isinstance(key, tuple) else (key,) - - if len(self.__parameters__) == 0: - raise TypeError(f"There are no type variables left in {self}") - elif len(key_tup) > len(self.__parameters__): - raise TypeError(f"Too many arguments for {self}") - elif len(key_tup) < len(self.__parameters__): - raise TypeError(f"Too few arguments for {self}") - - key_iter = iter(key_tup) - return _reconstruct_alias(self, key_iter) - - def __eq__(self, value: object) -> bool: - """Return ``self == value``.""" - if not isinstance(value, _GENERIC_ALIAS_TYPE): - return NotImplemented - return ( - self.__origin__ == value.__origin__ and - self.__args__ == value.__args__ and - self.__unpacked__ == getattr( - value, "__unpacked__", self.__unpacked__ - ) - ) - - def __iter__(self: _T) -> Generator[_T, None, None]: - """Return ``iter(self)``.""" - cls = type(self) - yield cls(self.__origin__, self.__args__, True) - - _ATTR_EXCEPTIONS: ClassVar[frozenset[str]] = frozenset({ - "__origin__", - "__args__", - "__parameters__", - "__mro_entries__", - "__reduce__", - "__reduce_ex__", - "__copy__", - "__deepcopy__", - "__unpacked__", - "__typing_unpacked_tuple_args__", - "__class__", - }) - - def __getattribute__(self, name: str) -> Any: - """Return ``getattr(self, name)``.""" - # Pull the attribute from `__origin__` unless its - # name is in `_ATTR_EXCEPTIONS` - cls = type(self) - if name in cls._ATTR_EXCEPTIONS: - return super().__getattribute__(name) - return getattr(self.__origin__, name) - - -# See `_GenericAlias.__eq__` -if sys.version_info >= (3, 9): - _GENERIC_ALIAS_TYPE = (_GenericAlias, types.GenericAlias) -else: - _GENERIC_ALIAS_TYPE = (_GenericAlias,) - -ScalarType = TypeVar("ScalarType", bound=np.generic, covariant=True) - -if TYPE_CHECKING or sys.version_info >= (3, 9): - _DType = np.dtype[ScalarType] - NDArray = np.ndarray[Any, np.dtype[ScalarType]] -else: - _DType = _GenericAlias(np.dtype, (ScalarType,)) - NDArray = _GenericAlias(np.ndarray, (Any, _DType)) diff --git a/numpy/typing/tests/test_generic_alias.py b/numpy/typing/tests/test_generic_alias.py deleted file mode 100644 index 861babd4b3ad..000000000000 --- a/numpy/typing/tests/test_generic_alias.py +++ /dev/null @@ -1,188 +0,0 @@ -from __future__ import annotations - -import sys -import copy -import types -import pickle -import weakref -from typing import TypeVar, Any, Union, Callable - -import pytest -import numpy as np -from numpy._typing._generic_alias import _GenericAlias -from typing_extensions import Unpack - -ScalarType = TypeVar("ScalarType", bound=np.generic, covariant=True) -T1 = TypeVar("T1") -T2 = TypeVar("T2") -DType = _GenericAlias(np.dtype, (ScalarType,)) -NDArray = _GenericAlias(np.ndarray, (Any, DType)) - -# NOTE: The `npt._GenericAlias` *class* isn't quite stable on python >=3.11. -# This is not a problem during runtime (as it's 3.8-exclusive), but we still -# need it for the >=3.9 in order to verify its semantics match -# `types.GenericAlias` replacement. xref numpy/numpy#21526 -if sys.version_info >= (3, 9): - DType_ref = types.GenericAlias(np.dtype, (ScalarType,)) - NDArray_ref = types.GenericAlias(np.ndarray, (Any, DType_ref)) - FuncType = Callable[["_GenericAlias | types.GenericAlias"], Any] -else: - DType_ref = Any - NDArray_ref = Any - FuncType = Callable[["_GenericAlias"], Any] - -GETATTR_NAMES = sorted(set(dir(np.ndarray)) - _GenericAlias._ATTR_EXCEPTIONS) - -BUFFER = np.array([1], dtype=np.int64) -BUFFER.setflags(write=False) - -def _get_subclass_mro(base: type) -> tuple[type, ...]: - class Subclass(base): # type: ignore[misc,valid-type] - pass - return Subclass.__mro__[1:] - - -class TestGenericAlias: - """Tests for `numpy._typing._generic_alias._GenericAlias`.""" - - @pytest.mark.parametrize("name,func", [ - ("__init__", lambda n: n), - ("__init__", lambda n: _GenericAlias(np.ndarray, Any)), - ("__init__", lambda n: _GenericAlias(np.ndarray, (Any,))), - ("__init__", lambda n: _GenericAlias(np.ndarray, (Any, Any))), - ("__init__", lambda n: _GenericAlias(np.ndarray, T1)), - ("__init__", lambda n: _GenericAlias(np.ndarray, (T1,))), - ("__init__", lambda n: _GenericAlias(np.ndarray, (T1, T2))), - ("__origin__", lambda n: n.__origin__), - ("__args__", lambda n: n.__args__), - ("__parameters__", lambda n: n.__parameters__), - ("__mro_entries__", lambda n: n.__mro_entries__([object])), - ("__hash__", lambda n: hash(n)), - ("__repr__", lambda n: repr(n)), - ("__getitem__", lambda n: n[np.float64]), - ("__getitem__", lambda n: n[ScalarType][np.float64]), - ("__getitem__", lambda n: n[Union[np.int64, ScalarType]][np.float64]), - ("__getitem__", lambda n: n[Union[T1, T2]][np.float32, np.float64]), - ("__eq__", lambda n: n == n), - ("__ne__", lambda n: n != np.ndarray), - ("__call__", lambda n: n((1,), np.int64, BUFFER)), - ("__call__", lambda n: n(shape=(1,), dtype=np.int64, buffer=BUFFER)), - ("subclassing", lambda n: _get_subclass_mro(n)), - ("pickle", lambda n: n == pickle.loads(pickle.dumps(n))), - ]) - def test_pass(self, name: str, func: FuncType) -> None: - """Compare `types.GenericAlias` with its numpy-based backport. - - Checker whether ``func`` runs as intended and that both `GenericAlias` - and `_GenericAlias` return the same result. - - """ - value = func(NDArray) - - if sys.version_info >= (3, 9): - value_ref = func(NDArray_ref) - assert value == value_ref - - @pytest.mark.parametrize("name,func", [ - ("__copy__", lambda n: n == copy.copy(n)), - ("__deepcopy__", lambda n: n == copy.deepcopy(n)), - ]) - def test_copy(self, name: str, func: FuncType) -> None: - value = func(NDArray) - - # xref bpo-45167 - GE_398 = ( - sys.version_info[:2] == (3, 9) and sys.version_info >= (3, 9, 8) - ) - if GE_398 or sys.version_info >= (3, 10, 1): - value_ref = func(NDArray_ref) - assert value == value_ref - - def test_dir(self) -> None: - value = dir(NDArray) - if sys.version_info < (3, 9): - return - - # A number attributes only exist in `types.GenericAlias` in >= 3.11 - if sys.version_info < (3, 11, 0, "beta", 3): - value.remove("__typing_unpacked_tuple_args__") - if sys.version_info < (3, 11, 0, "beta", 1): - value.remove("__unpacked__") - assert value == dir(NDArray_ref) - - @pytest.mark.parametrize("name,func,dev_version", [ - ("__iter__", lambda n: len(list(n)), ("beta", 1)), - ("__iter__", lambda n: next(iter(n)), ("beta", 1)), - ("__unpacked__", lambda n: n.__unpacked__, ("beta", 1)), - ("Unpack", lambda n: Unpack[n], ("beta", 1)), - - # The right operand should now have `__unpacked__ = True`, - # and they are thus now longer equivalent - ("__ne__", lambda n: n != next(iter(n)), ("beta", 1)), - - # >= beta3 - ("__typing_unpacked_tuple_args__", - lambda n: n.__typing_unpacked_tuple_args__, ("beta", 3)), - - # >= beta4 - ("__class__", lambda n: n.__class__ == type(n), ("beta", 4)), - ]) - def test_py311_features( - self, - name: str, - func: FuncType, - dev_version: tuple[str, int], - ) -> None: - """Test Python 3.11 features.""" - value = func(NDArray) - - if sys.version_info >= (3, 11, 0, *dev_version): - value_ref = func(NDArray_ref) - assert value == value_ref - - def test_weakref(self) -> None: - """Test ``__weakref__``.""" - value = weakref.ref(NDArray)() - - if sys.version_info >= (3, 9, 1): # xref bpo-42332 - value_ref = weakref.ref(NDArray_ref)() - assert value == value_ref - - @pytest.mark.parametrize("name", GETATTR_NAMES) - def test_getattr(self, name: str) -> None: - """Test that `getattr` wraps around the underlying type, - aka ``__origin__``. - - """ - value = getattr(NDArray, name) - value_ref1 = getattr(np.ndarray, name) - - if sys.version_info >= (3, 9): - value_ref2 = getattr(NDArray_ref, name) - assert value == value_ref1 == value_ref2 - else: - assert value == value_ref1 - - @pytest.mark.parametrize("name,exc_type,func", [ - ("__getitem__", TypeError, lambda n: n[()]), - ("__getitem__", TypeError, lambda n: n[Any, Any]), - ("__getitem__", TypeError, lambda n: n[Any][Any]), - ("isinstance", TypeError, lambda n: isinstance(np.array(1), n)), - ("issublass", TypeError, lambda n: issubclass(np.ndarray, n)), - ("setattr", AttributeError, lambda n: setattr(n, "__origin__", int)), - ("setattr", AttributeError, lambda n: setattr(n, "test", int)), - ("getattr", AttributeError, lambda n: getattr(n, "test")), - ]) - def test_raise( - self, - name: str, - exc_type: type[BaseException], - func: FuncType, - ) -> None: - """Test operations that are supposed to raise.""" - with pytest.raises(exc_type): - func(NDArray) - - if sys.version_info >= (3, 9): - with pytest.raises(exc_type): - func(NDArray_ref)