Skip to content

ENH: Add a typing protocol for representing nested sequences #19894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 61 additions & 247 deletions numpy/__init__.pyi

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions numpy/core/multiarray.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ from numpy.typing import (
NDArray,
ArrayLike,
_SupportsArray,
_NestedSequence,
_FiniteNestedSequence,
_ArrayLikeBool_co,
_ArrayLikeUInt_co,
_ArrayLikeInt_co,
Expand All @@ -91,7 +91,7 @@ _DTypeLike = Union[
Type[_SCT],
_SupportsDType[dtype[_SCT]],
]
_ArrayLike = _NestedSequence[_SupportsArray[dtype[_SCT]]]
_ArrayLike = _FiniteNestedSequence[_SupportsArray[dtype[_SCT]]]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used with typevars in def func(a: _ArrayLike[_T]) -> NDArray[_T]: ...-esque expressions, so unfortunately we can't use the new fancy protocol here.


# Valid time units
_UnitKind = L[
Expand Down
4 changes: 2 additions & 2 deletions numpy/core/shape_base.pyi
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from typing import TypeVar, overload, List, Sequence, Any, SupportsIndex

from numpy import generic, dtype
from numpy.typing import ArrayLike, NDArray, _NestedSequence, _SupportsArray
from numpy.typing import ArrayLike, NDArray, _FiniteNestedSequence, _SupportsArray

_SCT = TypeVar("_SCT", bound=generic)
_ArrayType = TypeVar("_ArrayType", bound=NDArray[Any])

_ArrayLike = _NestedSequence[_SupportsArray[dtype[_SCT]]]
_ArrayLike = _FiniteNestedSequence[_SupportsArray[dtype[_SCT]]]

__all__: List[str]

Expand Down
4 changes: 2 additions & 2 deletions numpy/lib/arraypad.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ from numpy.typing import (
ArrayLike,
NDArray,
_ArrayLikeInt,
_NestedSequence,
_FiniteNestedSequence,
_SupportsArray,
)

Expand Down Expand Up @@ -45,7 +45,7 @@ _ModeKind = L[
"empty",
]

_ArrayLike = _NestedSequence[_SupportsArray[dtype[_SCT]]]
_ArrayLike = _FiniteNestedSequence[_SupportsArray[dtype[_SCT]]]

__all__: List[str]

Expand Down
18 changes: 8 additions & 10 deletions numpy/lib/index_tricks.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ from numpy.typing import (
# Arrays
ArrayLike,
_NestedSequence,
_RecursiveSequence,
_FiniteNestedSequence,
NDArray,
_ArrayLikeInt,

Expand All @@ -59,21 +59,19 @@ _ArrayType = TypeVar("_ArrayType", bound=ndarray[Any, Any])
__all__: List[str]

@overload
def ix_(*args: _NestedSequence[_SupportsDType[_DType]]) -> Tuple[ndarray[Any, _DType], ...]: ...
def ix_(*args: _FiniteNestedSequence[_SupportsDType[_DType]]) -> Tuple[ndarray[Any, _DType], ...]: ...
@overload
def ix_(*args: _NestedSequence[str]) -> Tuple[NDArray[str_], ...]: ...
def ix_(*args: str | _NestedSequence[str]) -> Tuple[NDArray[str_], ...]: ...
@overload
def ix_(*args: _NestedSequence[bytes]) -> Tuple[NDArray[bytes_], ...]: ...
def ix_(*args: bytes | _NestedSequence[bytes]) -> Tuple[NDArray[bytes_], ...]: ...
@overload
def ix_(*args: _NestedSequence[bool]) -> Tuple[NDArray[bool_], ...]: ...
def ix_(*args: bool | _NestedSequence[bool]) -> Tuple[NDArray[bool_], ...]: ...
@overload
def ix_(*args: _NestedSequence[int]) -> Tuple[NDArray[int_], ...]: ...
def ix_(*args: int | _NestedSequence[int]) -> Tuple[NDArray[int_], ...]: ...
@overload
def ix_(*args: _NestedSequence[float]) -> Tuple[NDArray[float_], ...]: ...
def ix_(*args: float | _NestedSequence[float]) -> Tuple[NDArray[float_], ...]: ...
@overload
def ix_(*args: _NestedSequence[complex]) -> Tuple[NDArray[complex_], ...]: ...
@overload
def ix_(*args: _RecursiveSequence) -> Tuple[NDArray[Any], ...]: ...
def ix_(*args: complex | _NestedSequence[complex]) -> Tuple[NDArray[complex_], ...]: ...

class nd_grid(Generic[_BoolType]):
sparse: _BoolType
Expand Down
4 changes: 2 additions & 2 deletions numpy/lib/shape_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ from numpy.typing import (
ArrayLike,
NDArray,
_ShapeLike,
_NestedSequence,
_FiniteNestedSequence,
_SupportsDType,
_ArrayLikeBool_co,
_ArrayLikeUInt_co,
Expand All @@ -31,7 +31,7 @@ from numpy.core.shape_base import vstack

_SCT = TypeVar("_SCT", bound=generic)

_ArrayLike = _NestedSequence[_SupportsDType[dtype[_SCT]]]
_ArrayLike = _FiniteNestedSequence[_SupportsDType[dtype[_SCT]]]

# The signatures of `__array_wrap__` and `__array_prepare__` are the same;
# give them unique names for the sake of clarity
Expand Down
4 changes: 2 additions & 2 deletions numpy/lib/stride_tricks.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ from numpy.typing import (
ArrayLike,
_ShapeLike,
_Shape,
_NestedSequence,
_FiniteNestedSequence,
_SupportsArray,
)

_SCT = TypeVar("_SCT", bound=generic)
_ArrayLike = _NestedSequence[_SupportsArray[dtype[_SCT]]]
_ArrayLike = _FiniteNestedSequence[_SupportsArray[dtype[_SCT]]]

__all__: List[str]

Expand Down
4 changes: 2 additions & 2 deletions numpy/lib/twodim_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ from numpy.typing import (
_SupportsDType,
ArrayLike,
NDArray,
_NestedSequence,
_FiniteNestedSequence,
_SupportsArray,
_ArrayLikeInt_co,
_ArrayLikeFloat_co,
Expand All @@ -55,7 +55,7 @@ _DTypeLike = Union[
dtype[_SCT],
_SupportsDType[dtype[_SCT]],
]
_ArrayLike = _NestedSequence[_SupportsArray[dtype[_SCT]]]
_ArrayLike = _FiniteNestedSequence[_SupportsArray[dtype[_SCT]]]

__all__: List[str]

Expand Down
4 changes: 2 additions & 2 deletions numpy/lib/type_check.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ from numpy.typing import (
_64Bit,
_SupportsDType,
_ScalarLike_co,
_NestedSequence,
_FiniteNestedSequence,
_SupportsArray,
_DTypeLikeComplex,
)
Expand All @@ -39,7 +39,7 @@ _SCT = TypeVar("_SCT", bound=generic)
_NBit1 = TypeVar("_NBit1", bound=NBitBase)
_NBit2 = TypeVar("_NBit2", bound=NBitBase)

_ArrayLike = _NestedSequence[_SupportsArray[dtype[_SCT]]]
_ArrayLike = _FiniteNestedSequence[_SupportsArray[dtype[_SCT]]]

class _SupportsReal(Protocol[_T_co]):
@property
Expand Down
5 changes: 3 additions & 2 deletions numpy/typing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ class _32Bit(_64Bit): ... # type: ignore[misc]
class _16Bit(_32Bit): ... # type: ignore[misc]
class _8Bit(_16Bit): ... # type: ignore[misc]


from ._nested_sequence import _NestedSequence
from ._nbit import (
_NBitByte,
_NBitShort,
Expand Down Expand Up @@ -305,8 +307,7 @@ class _8Bit(_16Bit): ... # type: ignore[misc]
from ._array_like import (
ArrayLike as ArrayLike,
_ArrayLike,
_NestedSequence,
_RecursiveSequence,
_FiniteNestedSequence,
_SupportsArray,
_ArrayLikeInt,
_ArrayLikeBool_co,
Expand Down
33 changes: 21 additions & 12 deletions numpy/typing/_array_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
str_,
bytes_,
)
from ._nested_sequence import _NestedSequence

_T = TypeVar("_T")
_ScalarType = TypeVar("_ScalarType", bound=generic)
Expand All @@ -32,21 +33,23 @@
class _SupportsArray(Protocol[_DType_co]):
def __array__(self) -> ndarray[Any, _DType_co]: ...

# TODO: Wait for support for recursive types
_NestedSequence = Union[

# TODO: Wait until mypy supports recursive objects in combination with typevars
_FiniteNestedSequence = Union[
_T,
Sequence[_T],
Sequence[Sequence[_T]],
Sequence[Sequence[Sequence[_T]]],
Sequence[Sequence[Sequence[Sequence[_T]]]],
]
_RecursiveSequence = Sequence[Sequence[Sequence[Sequence[Sequence[Any]]]]]

# A union representing array-like objects; consists of two typevars:
# One representing types that can be parametrized w.r.t. `np.dtype`
# and another one for the rest
_ArrayLike = Union[
_SupportsArray[_DType],
_NestedSequence[_SupportsArray[_DType]],
_T,
_NestedSequence[_T],
]

Expand All @@ -57,12 +60,9 @@ def __array__(self) -> ndarray[Any, _DType_co]: ...
# is resolved. See also the mypy issue:
#
# https://github.com/python/typing/issues/593
ArrayLike = Union[
_RecursiveSequence,
_ArrayLike[
dtype,
Union[bool, int, float, complex, str, bytes]
],
ArrayLike = _ArrayLike[
dtype,
Union[bool, int, float, complex, str, bytes],
]

# `ArrayLike<X>_co`: array-like objects that can be coerced into `X`
Expand Down Expand Up @@ -95,10 +95,19 @@ def __array__(self) -> ndarray[Any, _DType_co]: ...
"dtype[Union[bool_, integer[Any], timedelta64]]",
Union[bool, int],
]
_ArrayLikeDT64_co = _NestedSequence[_SupportsArray["dtype[datetime64]"]]
_ArrayLikeObject_co = _NestedSequence[_SupportsArray["dtype[object_]"]]
_ArrayLikeDT64_co = Union[
_SupportsArray["dtype[datetime64]"],
_NestedSequence[_SupportsArray["dtype[datetime64]"]],
]
_ArrayLikeObject_co = Union[
_SupportsArray["dtype[object_]"],
_NestedSequence[_SupportsArray["dtype[object_]"]],
]

_ArrayLikeVoid_co = _NestedSequence[_SupportsArray["dtype[void]"]]
_ArrayLikeVoid_co = Union[
_SupportsArray["dtype[void]"],
_NestedSequence[_SupportsArray["dtype[void]"]],
]
_ArrayLikeStr_co = _ArrayLike[
"dtype[str_]",
str,
Expand Down
93 changes: 93 additions & 0 deletions numpy/typing/_nested_sequence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""A module containing the `_NestedSequence` protocol."""

from __future__ import annotations

import sys
from typing import (
Any,
Iterator,
overload,
TypeVar,
Protocol,
)

__all__ = ["_NestedSequence"]

_T_co = TypeVar("_T_co", covariant=True)


class _NestedSequence(Protocol[_T_co]):
"""A protocol for representing nested sequences.

Warning
-------
`_NestedSequence` currently does not work in combination with typevars,
*e.g.* ``def func(a: _NestedSequnce[T]) -> T: ...``.

See Also
--------
`collections.abc.Sequence`
ABCs for read-only and mutable :term:`sequences`.

Examples
--------
.. code-block:: python

>>> from __future__ import annotations

>>> from typing import TYPE_CHECKING
>>> import numpy as np
>>> from numpy.typing import _NestedSequnce

>>> def get_dtype(seq: _NestedSequnce[float]) -> np.dtype[np.float64]:
... return np.asarray(seq).dtype

>>> a = get_dtype([1.0])
>>> b = get_dtype([[1.0]])
>>> c = get_dtype([[[1.0]]])
>>> d = get_dtype([[[[1.0]]]])

>>> if TYPE_CHECKING:
... reveal_locals()
... # note: Revealed local types are:
... # note: a: numpy.dtype[numpy.floating[numpy.typing._64Bit]]
... # note: b: numpy.dtype[numpy.floating[numpy.typing._64Bit]]
... # note: c: numpy.dtype[numpy.floating[numpy.typing._64Bit]]
... # note: d: numpy.dtype[numpy.floating[numpy.typing._64Bit]]

"""

def __len__(self, /) -> int:
"""Implement ``len(self)``."""
raise NotImplementedError

@overload
def __getitem__(self, index: int, /) -> _T_co | _NestedSequence[_T_co]: ...
@overload
def __getitem__(self, index: slice, /) -> _NestedSequence[_T_co]: ...

def __getitem__(self, index, /):
"""Implement ``self[x]``."""
raise NotImplementedError

def __contains__(self, x: object, /) -> bool:
"""Implement ``x in self``."""
raise NotImplementedError

def __iter__(self, /) -> Iterator[_T_co | _NestedSequence[_T_co]]:
"""Implement ``iter(self)``."""
raise NotImplementedError

def __reversed__(self, /) -> Iterator[_T_co | _NestedSequence[_T_co]]:
"""Implement ``reversed(self)``."""
raise NotImplementedError

def count(self, value: Any, /) -> int:
"""Return the number of occurrences of `value`."""
raise NotImplementedError

def index(
self, value: Any, start: int = 0, stop: int = sys.maxsize, /
) -> int:
"""Return the first index of `value`."""
raise NotImplementedError
4 changes: 2 additions & 2 deletions numpy/typing/tests/data/fail/comparisons.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
AR_M > AR_m # E: Unsupported operand types

# Unfortunately `NoReturn` errors are not the most descriptive
_1 = AR_i > str() # E: Need type annotation
_1 = AR_i > str() # E: No overload variant
_2 = AR_i > bytes() # E: Need type annotation
_3 = str() > AR_M # E: Need type annotation
_3 = str() > AR_M # E: Unsupported operand types
_4 = bytes() > AR_M # E: Need type annotation
2 changes: 1 addition & 1 deletion numpy/typing/tests/data/fail/multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def func(a: int) -> None: ...

np.busday_offset("2012", 10) # E: incompatible type

np.datetime_as_string("2012") # E: incompatible type
np.datetime_as_string("2012") # E: No overload variant

np.compare_chararrays("a", b"a", "==", False) # E: No overload variant

Expand Down
17 changes: 17 additions & 0 deletions numpy/typing/tests/data/fail/nested_sequence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Sequence, Tuple, List
import numpy.typing as npt

a: Sequence[float]
b: List[complex]
c: Tuple[str, ...]
d: int
e: str

def func(a: npt._NestedSequence[int]) -> None:
...

reveal_type(func(a)) # E: incompatible type
reveal_type(func(b)) # E: incompatible type
reveal_type(func(c)) # E: incompatible type
reveal_type(func(d)) # E: incompatible type
reveal_type(func(e)) # E: incompatible type
Loading