diff --git a/doc/release/upcoming_changes/28884.deprecation.rst b/doc/release/upcoming_changes/28884.deprecation.rst new file mode 100644 index 000000000000..c1be55fb0dd3 --- /dev/null +++ b/doc/release/upcoming_changes/28884.deprecation.rst @@ -0,0 +1,28 @@ +``numpy.typing.NBitBase`` deprecation +------------------------------------- +The ``numpy.typing.NBitBase`` type has been deprecated and will be removed in a future version. + +This type was previously intended to be used as a generic upper bound for type-parameters, for example: + +.. code-block:: python + + import numpy as np + import numpy.typing as npt + + def f[NT: npt.NBitBase](x: np.complexfloating[NT]) -> np.floating[NT]: ... + +But in NumPy 2.2.0, ``float64`` and ``complex128`` were changed to concrete subtypes, causing static type-checkers to reject ``x: np.float64 = f(np.complex128(42j))``. + +So instead, the better approach is to use ``typing.overload``: + +.. code-block:: python + + import numpy as np + from typing import overload + + @overload + def f(x: np.complex64) -> np.float32: ... + @overload + def f(x: np.complex128) -> np.float64: ... + @overload + def f(x: np.clongdouble) -> np.longdouble: ... diff --git a/numpy/_typing/__init__.py b/numpy/_typing/__init__.py index 92cdcec84900..097e59390691 100644 --- a/numpy/_typing/__init__.py +++ b/numpy/_typing/__init__.py @@ -6,7 +6,7 @@ _NestedSequence as _NestedSequence, ) from ._nbit_base import ( - NBitBase as NBitBase, + NBitBase as NBitBase, # pyright: ignore[reportDeprecated] _8Bit as _8Bit, _16Bit as _16Bit, _32Bit as _32Bit, diff --git a/numpy/_typing/_nbit_base.py b/numpy/_typing/_nbit_base.py index bf16c436c6da..df2fb64e4040 100644 --- a/numpy/_typing/_nbit_base.py +++ b/numpy/_typing/_nbit_base.py @@ -9,13 +9,17 @@ class NBitBase: """ A type representing `numpy.number` precision during static type checking. - Used exclusively for the purpose static type checking, `NBitBase` + Used exclusively for the purpose of static type checking, `NBitBase` represents the base of a hierarchical set of subclasses. Each subsequent subclass is herein used for representing a lower level of precision, *e.g.* ``64Bit > 32Bit > 16Bit``. .. versionadded:: 1.20 + .. deprecated:: 2.3 + Use ``@typing.overload`` or a ``TypeVar`` with a scalar-type as upper + bound, instead. + Examples -------- Below is a typical usage example: `NBitBase` is herein used for annotating @@ -48,6 +52,7 @@ class NBitBase: ... # note: out: numpy.floating[numpy.typing._64Bit*] """ + # Deprecated in NumPy 2.3, 2025-05-01 def __init_subclass__(cls) -> None: allowed_names = { diff --git a/numpy/_typing/_nbit_base.pyi b/numpy/_typing/_nbit_base.pyi new file mode 100644 index 000000000000..ccf8f5ceac45 --- /dev/null +++ b/numpy/_typing/_nbit_base.pyi @@ -0,0 +1,40 @@ +# pyright: reportDeprecated=false +# pyright: reportGeneralTypeIssues=false +# mypy: disable-error-code=misc + +from typing import final + +from typing_extensions import deprecated + +# Deprecated in NumPy 2.3, 2025-05-01 +@deprecated( + "`NBitBase` is deprecated and will be removed from numpy.typing in the " + "future. Use `@typing.overload` or a `TypeVar` with a scalar-type as upper " + "bound, instead. (deprecated in NumPy 2.3)", +) +@final +class NBitBase: ... + +@final +class _256Bit(NBitBase): ... + +@final +class _128Bit(_256Bit): ... + +@final +class _96Bit(_128Bit): ... + +@final +class _80Bit(_96Bit): ... + +@final +class _64Bit(_80Bit): ... + +@final +class _32Bit(_64Bit): ... + +@final +class _16Bit(_32Bit): ... + +@final +class _8Bit(_16Bit): ... diff --git a/numpy/typing/__init__.py b/numpy/typing/__init__.py index 8a4a974a9928..2c75c348667e 100644 --- a/numpy/typing/__init__.py +++ b/numpy/typing/__init__.py @@ -155,15 +155,40 @@ # NOTE: The API section will be appended with additional entries # further down in this file -from numpy._typing import ( - ArrayLike, - DTypeLike, - NBitBase, - NDArray, -) +# pyright: reportDeprecated=false + +from numpy._typing import ArrayLike, DTypeLike, NBitBase, NDArray __all__ = ["ArrayLike", "DTypeLike", "NBitBase", "NDArray"] + +__DIR = __all__ + [k for k in globals() if k.startswith("__") and k.endswith("__")] +__DIR_SET = frozenset(__DIR) + + +def __dir__() -> list[str]: + return __DIR + +def __getattr__(name: str): + if name == "NBitBase": + import warnings + + # Deprecated in NumPy 2.3, 2025-05-01 + warnings.warn( + "`NBitBase` is deprecated and will be removed from numpy.typing in the " + "future. Use `@typing.overload` or a `TypeVar` with a scalar-type as upper " + "bound, instead. (deprecated in NumPy 2.3)", + DeprecationWarning, + stacklevel=2, + ) + return NBitBase + + if name in __DIR_SET: + return globals()[name] + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + if __doc__ is not None: from numpy._typing._add_docstring import _docstrings __doc__ += _docstrings