diff --git a/doc/release/upcoming_changes/16515.improvement.rst b/doc/release/upcoming_changes/16515.improvement.rst new file mode 100644 index 000000000000..5b3803429d4a --- /dev/null +++ b/doc/release/upcoming_changes/16515.improvement.rst @@ -0,0 +1,8 @@ +NumPy is now typed +------------------ +Type annotations have been added for large parts of NumPy. There is +also a new `numpy.typing` module that contains useful types for +end-users. The currently available types are + +- ``ArrayLike``: for objects that can be coerced to an array +- ``DtypeLike``: for objects that can be coerced to a dtype diff --git a/numpy/__init__.pyi b/numpy/__init__.pyi new file mode 100644 index 000000000000..5031893edda9 --- /dev/null +++ b/numpy/__init__.pyi @@ -0,0 +1,1077 @@ +import builtins +import sys +import datetime as dt +from abc import abstractmethod + +from numpy.core._internal import _ctypes +from numpy.typing import ArrayLike, DtypeLike, _Shape, _ShapeLike + +from typing import ( + Any, + ByteString, + Callable, + Container, + Callable, + Dict, + Generic, + IO, + Iterable, + List, + Mapping, + Optional, + overload, + Sequence, + Sized, + SupportsAbs, + SupportsComplex, + SupportsFloat, + SupportsInt, + Text, + Tuple, + Type, + TypeVar, + Union, +) + +if sys.version_info[0] < 3: + class SupportsBytes: ... + +else: + from typing import SupportsBytes + +if sys.version_info >= (3, 8): + from typing import Literal, Protocol +else: + from typing_extensions import Literal, Protocol + +# TODO: remove when the full numpy namespace is defined +def __getattr__(name: str) -> Any: ... + +_NdArraySubClass = TypeVar("_NdArraySubClass", bound=ndarray) + +class dtype: + names: Optional[Tuple[str, ...]] + def __init__(self, obj: DtypeLike, align: bool = ..., copy: bool = ...) -> None: ... + def __eq__(self, other: DtypeLike) -> bool: ... + def __ne__(self, other: DtypeLike) -> bool: ... + def __gt__(self, other: DtypeLike) -> bool: ... + def __ge__(self, other: DtypeLike) -> bool: ... + def __lt__(self, other: DtypeLike) -> bool: ... + def __le__(self, other: DtypeLike) -> bool: ... + @property + def alignment(self) -> int: ... + @property + def base(self) -> dtype: ... + @property + def byteorder(self) -> str: ... + @property + def char(self) -> str: ... + @property + def descr(self) -> List[Union[Tuple[str, str], Tuple[str, str, _Shape]]]: ... + @property + def fields( + self, + ) -> Optional[Mapping[str, Union[Tuple[dtype, int], Tuple[dtype, int, Any]]]]: ... + @property + def flags(self) -> int: ... + @property + def hasobject(self) -> bool: ... + @property + def isbuiltin(self) -> int: ... + @property + def isnative(self) -> bool: ... + @property + def isalignedstruct(self) -> bool: ... + @property + def itemsize(self) -> int: ... + @property + def kind(self) -> str: ... + @property + def metadata(self) -> Optional[Mapping[str, Any]]: ... + @property + def name(self) -> str: ... + @property + def num(self) -> int: ... + @property + def shape(self) -> _Shape: ... + @property + def ndim(self) -> int: ... + @property + def subdtype(self) -> Optional[Tuple[dtype, _Shape]]: ... + def newbyteorder(self, new_order: str = ...) -> dtype: ... + # Leave str and type for end to avoid having to use `builtins.str` + # everywhere. See https://github.com/python/mypy/issues/3775 + @property + def str(self) -> builtins.str: ... + @property + def type(self) -> Type[generic]: ... + +_Dtype = dtype # to avoid name conflicts with ndarray.dtype + +class _flagsobj: + aligned: bool + updateifcopy: bool + writeable: bool + writebackifcopy: bool + @property + def behaved(self) -> bool: ... + @property + def c_contiguous(self) -> bool: ... + @property + def carray(self) -> bool: ... + @property + def contiguous(self) -> bool: ... + @property + def f_contiguous(self) -> bool: ... + @property + def farray(self) -> bool: ... + @property + def fnc(self) -> bool: ... + @property + def forc(self) -> bool: ... + @property + def fortran(self) -> bool: ... + @property + def num(self) -> int: ... + @property + def owndata(self) -> bool: ... + def __getitem__(self, key: str) -> bool: ... + def __setitem__(self, key: str, value: bool) -> None: ... + +_FlatIterSelf = TypeVar("_FlatIterSelf", bound=flatiter) + +class flatiter(Generic[_ArraySelf]): + @property + def base(self) -> _ArraySelf: ... + @property + def coords(self) -> _Shape: ... + @property + def index(self) -> int: ... + def copy(self) -> _ArraySelf: ... + def __iter__(self: _FlatIterSelf) -> _FlatIterSelf: ... + def __next__(self) -> generic: ... + +_ArraySelf = TypeVar("_ArraySelf", bound=_ArrayOrScalarCommon) + +class _ArrayOrScalarCommon( + SupportsInt, SupportsFloat, SupportsComplex, SupportsBytes, SupportsAbs[Any] +): + @property + def T(self: _ArraySelf) -> _ArraySelf: ... + @property + def base(self) -> Optional[ndarray]: ... + @property + def dtype(self) -> _Dtype: ... + @property + def data(self) -> memoryview: ... + @property + def flags(self) -> _flagsobj: ... + @property + def size(self) -> int: ... + @property + def itemsize(self) -> int: ... + @property + def nbytes(self) -> int: ... + @property + def ndim(self) -> int: ... + @property + def shape(self) -> _Shape: ... + @property + def strides(self) -> _Shape: ... + def __array__(self, __dtype: DtypeLike = ...) -> ndarray: ... + def __int__(self) -> int: ... + def __float__(self) -> float: ... + def __complex__(self) -> complex: ... + if sys.version_info[0] < 3: + def __oct__(self) -> str: ... + def __hex__(self) -> str: ... + def __nonzero__(self) -> bool: ... + def __unicode__(self) -> Text: ... + else: + def __bool__(self) -> bool: ... + def __bytes__(self) -> bytes: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __copy__(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + def __deepcopy__(self: _ArraySelf, memo: dict) -> _ArraySelf: ... + def __lt__(self, other): ... + def __le__(self, other): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __gt__(self, other): ... + def __ge__(self, other): ... + def __add__(self, other): ... + def __radd__(self, other): ... + def __iadd__(self, other): ... + def __sub__(self, other): ... + def __rsub__(self, other): ... + def __isub__(self, other): ... + def __mul__(self, other): ... + def __rmul__(self, other): ... + def __imul__(self, other): ... + if sys.version_info[0] < 3: + def __div__(self, other): ... + def __rdiv__(self, other): ... + def __idiv__(self, other): ... + def __truediv__(self, other): ... + def __rtruediv__(self, other): ... + def __itruediv__(self, other): ... + def __floordiv__(self, other): ... + def __rfloordiv__(self, other): ... + def __ifloordiv__(self, other): ... + def __mod__(self, other): ... + def __rmod__(self, other): ... + def __imod__(self, other): ... + def __divmod__(self, other): ... + def __rdivmod__(self, other): ... + # NumPy's __pow__ doesn't handle a third argument + def __pow__(self, other): ... + def __rpow__(self, other): ... + def __ipow__(self, other): ... + def __lshift__(self, other): ... + def __rlshift__(self, other): ... + def __ilshift__(self, other): ... + def __rshift__(self, other): ... + def __rrshift__(self, other): ... + def __irshift__(self, other): ... + def __and__(self, other): ... + def __rand__(self, other): ... + def __iand__(self, other): ... + def __xor__(self, other): ... + def __rxor__(self, other): ... + def __ixor__(self, other): ... + def __or__(self, other): ... + def __ror__(self, other): ... + def __ior__(self, other): ... + if sys.version_info[:2] >= (3, 5): + def __matmul__(self, other): ... + def __rmatmul__(self, other): ... + def __neg__(self: _ArraySelf) -> _ArraySelf: ... + def __pos__(self: _ArraySelf) -> _ArraySelf: ... + def __abs__(self: _ArraySelf) -> _ArraySelf: ... + def __invert__(self: _ArraySelf) -> _ArraySelf: ... + # TODO(shoyer): remove when all methods are defined + def __getattr__(self, name) -> Any: ... + +_BufferType = Union[ndarray, bytes, bytearray, memoryview] + +class ndarray(_ArrayOrScalarCommon, Iterable, Sized, Container): + @property + def real(self: _ArraySelf) -> _ArraySelf: ... + @real.setter + def real(self, value: ArrayLike) -> None: ... + @property + def imag(self: _ArraySelf) -> _ArraySelf: ... + @imag.setter + def imag(self, value: ArrayLike) -> None: ... + def __new__( + cls: Type[_ArraySelf], + shape: Sequence[int], + dtype: DtypeLike = ..., + buffer: _BufferType = ..., + offset: int = ..., + strides: _ShapeLike = ..., + order: Optional[str] = ..., + ) -> _ArraySelf: ... + @property + def dtype(self) -> _Dtype: ... + @property + def ctypes(self) -> _ctypes: ... + @property + def shape(self) -> _Shape: ... + @shape.setter + def shape(self, value: _ShapeLike): ... + @property + def flat(self: _ArraySelf) -> flatiter[_ArraySelf]: ... + @property + def strides(self) -> _Shape: ... + @strides.setter + def strides(self, value: _ShapeLike): ... + # Array conversion + @overload + def item(self, *args: int) -> Any: ... + @overload + def item(self, args: Tuple[int, ...]) -> Any: ... + def tolist(self) -> List[Any]: ... + @overload + def itemset(self, __value: Any) -> None: ... + @overload + def itemset(self, __item: _ShapeLike, __value: Any) -> None: ... + def tobytes(self, order: Optional[str] = ...) -> bytes: ... + def tofile( + self, fid: Union[IO[bytes], str], sep: str = ..., format: str = ... + ) -> None: ... + def dump(self, file: str) -> None: ... + def dumps(self) -> bytes: ... + def astype( + self: _ArraySelf, + dtype: DtypeLike, + order: str = ..., + casting: str = ..., + subok: bool = ..., + copy: bool = ..., + ) -> _ArraySelf: ... + def byteswap(self: _ArraySelf, inplace: bool = ...) -> _ArraySelf: ... + def copy(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + @overload + def view(self, type: Type[_NdArraySubClass]) -> _NdArraySubClass: ... + @overload + def view(self: _ArraySelf, dtype: DtypeLike = ...) -> _ArraySelf: ... + @overload + def view( + self, dtype: DtypeLike, type: Type[_NdArraySubClass] + ) -> _NdArraySubClass: ... + def getfield( + self: _ArraySelf, dtype: DtypeLike, offset: int = ... + ) -> _ArraySelf: ... + def setflags( + self, write: bool = ..., align: bool = ..., uic: bool = ... + ) -> None: ... + def fill(self, value: Any) -> None: ... + # Shape manipulation + @overload + def reshape( + self: _ArraySelf, shape: Sequence[int], *, order: str = ... + ) -> _ArraySelf: ... + @overload + def reshape(self: _ArraySelf, *shape: int, order: str = ...) -> _ArraySelf: ... + @overload + def resize(self, new_shape: Sequence[int], *, refcheck: bool = ...) -> None: ... + @overload + def resize(self, *new_shape: int, refcheck: bool = ...) -> None: ... + @overload + def transpose(self: _ArraySelf, axes: Sequence[int]) -> _ArraySelf: ... + @overload + def transpose(self: _ArraySelf, *axes: int) -> _ArraySelf: ... + def swapaxes(self: _ArraySelf, axis1: int, axis2: int) -> _ArraySelf: ... + def flatten(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + def ravel(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + def squeeze( + self: _ArraySelf, axis: Union[int, Tuple[int, ...]] = ... + ) -> _ArraySelf: ... + # Many of these special methods are irrelevant currently, since protocols + # aren't supported yet. That said, I'm adding them for completeness. + # https://docs.python.org/3/reference/datamodel.html + def __len__(self) -> int: ... + def __getitem__(self, key) -> Any: ... + def __setitem__(self, key, value): ... + def __iter__(self) -> Any: ... + def __contains__(self, key) -> bool: ... + def __index__(self) -> int: ... + +# NOTE: while `np.generic` is not technically an instance of `ABCMeta`, +# the `@abstractmethod` decorator is herein used to (forcefully) deny +# the creation of `np.generic` instances. +# The `# type: ignore` comments are necessary to silence mypy errors regarding +# the missing `ABCMeta` metaclass. + +# See https://github.com/numpy/numpy-stubs/pull/80 for more details. + +class generic(_ArrayOrScalarCommon): + @abstractmethod + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + @property + def base(self) -> None: ... + +class _real_generic(generic): # type: ignore + @property + def real(self: _ArraySelf) -> _ArraySelf: ... + @property + def imag(self: _ArraySelf) -> _ArraySelf: ... + +class number(generic): ... # type: ignore + +class bool_(_real_generic): + def __init__(self, value: object = ...) -> None: ... + +class object_(generic): + def __init__(self, value: object = ...) -> None: ... + +class datetime64: + @overload + def __init__( + self, _data: Union[datetime64, str, dt.datetime] = ..., _format: str = ... + ) -> None: ... + @overload + def __init__(self, _data: int, _format: str) -> None: ... + def __add__(self, other: Union[timedelta64, int]) -> datetime64: ... + def __sub__(self, other: Union[timedelta64, datetime64, int]) -> timedelta64: ... + +class integer(number, _real_generic): ... # type: ignore +class signedinteger(integer): ... # type: ignore + +class int8(signedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class int16(signedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class int32(signedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class int64(signedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class timedelta64(signedinteger): + def __init__(self, _data: Any = ..., _format: str = ...) -> None: ... + @overload + def __add__(self, other: Union[timedelta64, int]) -> timedelta64: ... + @overload + def __add__(self, other: datetime64) -> datetime64: ... + def __sub__(self, other: Union[timedelta64, int]) -> timedelta64: ... + if sys.version_info[0] < 3: + @overload + def __div__(self, other: timedelta64) -> float: ... + @overload + def __div__(self, other: float) -> timedelta64: ... + @overload + def __truediv__(self, other: timedelta64) -> float: ... + @overload + def __truediv__(self, other: float) -> timedelta64: ... + def __mod__(self, other: timedelta64) -> timedelta64: ... + +class unsignedinteger(integer): ... # type: ignore + +class uint8(unsignedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class uint16(unsignedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class uint32(unsignedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class uint64(unsignedinteger): + def __init__(self, value: SupportsInt = ...) -> None: ... + +class inexact(number): ... # type: ignore +class floating(inexact, _real_generic): ... # type: ignore + +class float16(floating): + def __init__(self, value: SupportsFloat = ...) -> None: ... + +class float32(floating): + def __init__(self, value: SupportsFloat = ...) -> None: ... + +class float64(floating): + def __init__(self, value: SupportsFloat = ...) -> None: ... + +class complexfloating(inexact): ... # type: ignore + +class complex64(complexfloating): + def __init__( + self, value: Union[SupportsInt, SupportsFloat, SupportsComplex] = ... + ) -> None: ... + @property + def real(self) -> float32: ... + @property + def imag(self) -> float32: ... + +class complex128(complexfloating): + def __init__( + self, value: Union[SupportsInt, SupportsFloat, SupportsComplex] = ... + ) -> None: ... + @property + def real(self) -> float64: ... + @property + def imag(self) -> float64: ... + +class flexible(_real_generic): ... # type: ignore + +class void(flexible): + def __init__(self, value: Union[int, integer, bool_, bytes, bytes_]): ... + +class character(_real_generic): ... # type: ignore + +class bytes_(character): + @overload + def __init__(self, value: object = ...) -> None: ... + @overload + def __init__( + self, value: object, encoding: str = ..., errors: str = ... + ) -> None: ... + +class str_(character): + @overload + def __init__(self, value: object = ...) -> None: ... + @overload + def __init__( + self, value: object, encoding: str = ..., errors: str = ... + ) -> None: ... + +# TODO(alan): Platform dependent types +# longcomplex, longdouble, longfloat +# bytes, short, intc, intp, longlong +# half, single, double, longdouble +# uint_, int_, float_, complex_ +# float128, complex256 +# float96 + +def array( + object: object, + dtype: DtypeLike = ..., + copy: bool = ..., + subok: bool = ..., + ndmin: int = ..., +) -> ndarray: ... +def zeros( + shape: _ShapeLike, dtype: DtypeLike = ..., order: Optional[str] = ... +) -> ndarray: ... +def ones( + shape: _ShapeLike, dtype: DtypeLike = ..., order: Optional[str] = ... +) -> ndarray: ... +def empty( + shape: _ShapeLike, dtype: DtypeLike = ..., order: Optional[str] = ... +) -> ndarray: ... +def zeros_like( + a: ArrayLike, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[Union[int, Sequence[int]]] = ..., +) -> ndarray: ... +def ones_like( + a: ArrayLike, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[_ShapeLike] = ..., +) -> ndarray: ... +def empty_like( + a: ArrayLike, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[_ShapeLike] = ..., +) -> ndarray: ... +def full( + shape: _ShapeLike, fill_value: Any, dtype: DtypeLike = ..., order: str = ... +) -> ndarray: ... +def full_like( + a: ArrayLike, + fill_value: Any, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[_ShapeLike] = ..., +) -> ndarray: ... +def count_nonzero( + a: ArrayLike, axis: Optional[Union[int, Tuple[int], Tuple[int, int]]] = ... +) -> Union[int, ndarray]: ... +def isfortran(a: ndarray) -> bool: ... +def argwhere(a: ArrayLike) -> ndarray: ... +def flatnonzero(a: ArrayLike) -> ndarray: ... +def correlate(a: ArrayLike, v: ArrayLike, mode: str = ...) -> ndarray: ... +def convolve(a: ArrayLike, v: ArrayLike, mode: str = ...) -> ndarray: ... +def outer(a: ArrayLike, b: ArrayLike, out: ndarray = ...) -> ndarray: ... +def tensordot( + a: ArrayLike, + b: ArrayLike, + axes: Union[ + int, Tuple[int, int], Tuple[Tuple[int, int], ...], Tuple[List[int, int], ...] + ] = ..., +) -> ndarray: ... +def roll( + a: ArrayLike, + shift: Union[int, Tuple[int, ...]], + axis: Optional[Union[int, Tuple[int, ...]]] = ..., +) -> ndarray: ... +def rollaxis(a: ArrayLike, axis: int, start: int = ...) -> ndarray: ... +def moveaxis( + a: ndarray, + source: Union[int, Sequence[int]], + destination: Union[int, Sequence[int]], +) -> ndarray: ... +def cross( + a: ArrayLike, + b: ArrayLike, + axisa: int = ..., + axisb: int = ..., + axisc: int = ..., + axis: Optional[int] = ..., +) -> ndarray: ... +def indices( + dimensions: Sequence[int], dtype: dtype = ..., sparse: bool = ... +) -> Union[ndarray, Tuple[ndarray, ...]]: ... +def fromfunction(function: Callable, shape: Tuple[int, int], **kwargs) -> Any: ... +def isscalar(element: Any) -> bool: ... +def binary_repr(num: int, width: Optional[int] = ...) -> str: ... +def base_repr(number: int, base: int = ..., padding: int = ...) -> str: ... +def identity(n: int, dtype: DtypeLike = ...) -> ndarray: ... +def allclose( + a: ArrayLike, + b: ArrayLike, + rtol: float = ..., + atol: float = ..., + equal_nan: bool = ..., +) -> bool: ... +def isclose( + a: ArrayLike, + b: ArrayLike, + rtol: float = ..., + atol: float = ..., + equal_nan: bool = ..., +) -> Union[bool_, ndarray]: ... +def array_equal(a1: ArrayLike, a2: ArrayLike) -> bool: ... +def array_equiv(a1: ArrayLike, a2: ArrayLike) -> bool: ... + +# +# Constants +# + +Inf: float +Infinity: float +NAN: float +NINF: float +NZERO: float +NaN: float +PINF: float +PZERO: float +e: float +euler_gamma: float +inf: float +infty: float +nan: float +pi: float + +ALLOW_THREADS: int +BUFSIZE: int +CLIP: int +ERR_CALL: int +ERR_DEFAULT: int +ERR_IGNORE: int +ERR_LOG: int +ERR_PRINT: int +ERR_RAISE: int +ERR_WARN: int +FLOATING_POINT_SUPPORT: int +FPE_DIVIDEBYZERO: int +FPE_INVALID: int +FPE_OVERFLOW: int +FPE_UNDERFLOW: int +MAXDIMS: int +MAY_SHARE_BOUNDS: int +MAY_SHARE_EXACT: int +RAISE: int +SHIFT_DIVIDEBYZERO: int +SHIFT_INVALID: int +SHIFT_OVERFLOW: int +SHIFT_UNDERFLOW: int +UFUNC_BUFSIZE_DEFAULT: int +WRAP: int +little_endian: int +tracemalloc_domain: int + +class ufunc: + @property + def __name__(self) -> str: ... + def __call__( + self, + *args: ArrayLike, + out: Optional[Union[ndarray, Tuple[ndarray, ...]]] = ..., + where: Optional[ndarray] = ..., + # The list should be a list of tuples of ints, but since we + # don't know the signature it would need to be + # Tuple[int, ...]. But, since List is invariant something like + # e.g. List[Tuple[int, int]] isn't a subtype of + # List[Tuple[int, ...]], so we can't type precisely here. + axes: List[Any] = ..., + axis: int = ..., + keepdims: bool = ..., + # TODO: make this precise when we can use Literal. + casting: str = ..., + # TODO: make this precise when we can use Literal. + order: Optional[str] = ..., + dtype: DtypeLike = ..., + subok: bool = ..., + signature: Union[str, Tuple[str]] = ..., + # In reality this should be a length of list 3 containing an + # int, an int, and a callable, but there's no way to express + # that. + extobj: List[Union[int, Callable]] = ..., + ) -> Union[ndarray, generic]: ... + @property + def nin(self) -> int: ... + @property + def nout(self) -> int: ... + @property + def nargs(self) -> int: ... + @property + def ntypes(self) -> int: ... + @property + def types(self) -> List[str]: ... + # Broad return type because it has to encompass things like + # + # >>> np.logical_and.identity is True + # True + # >>> np.add.identity is 0 + # True + # >>> np.sin.identity is None + # True + # + # and any user-defined ufuncs. + @property + def identity(self) -> Any: ... + # This is None for ufuncs and a string for gufuncs. + @property + def signature(self) -> Optional[str]: ... + # The next four methods will always exist, but they will just + # raise a ValueError ufuncs with that don't accept two input + # arguments and return one output argument. Because of that we + # can't type them very precisely. + @property + def reduce(self) -> Any: ... + @property + def accumulate(self) -> Any: ... + @property + def reduceat(self) -> Any: ... + @property + def outer(self) -> Any: ... + # Similarly at won't be defined for ufuncs that return multiple + # outputs, so we can't type it very precisely. + @property + def at(self) -> Any: ... + +absolute: ufunc +add: ufunc +arccos: ufunc +arccosh: ufunc +arcsin: ufunc +arcsinh: ufunc +arctan2: ufunc +arctan: ufunc +arctanh: ufunc +bitwise_and: ufunc +bitwise_or: ufunc +bitwise_xor: ufunc +cbrt: ufunc +ceil: ufunc +conjugate: ufunc +copysign: ufunc +cos: ufunc +cosh: ufunc +deg2rad: ufunc +degrees: ufunc +divmod: ufunc +equal: ufunc +exp2: ufunc +exp: ufunc +expm1: ufunc +fabs: ufunc +float_power: ufunc +floor: ufunc +floor_divide: ufunc +fmax: ufunc +fmin: ufunc +fmod: ufunc +frexp: ufunc +gcd: ufunc +greater: ufunc +greater_equal: ufunc +heaviside: ufunc +hypot: ufunc +invert: ufunc +isfinite: ufunc +isinf: ufunc +isnan: ufunc +isnat: ufunc +lcm: ufunc +ldexp: ufunc +left_shift: ufunc +less: ufunc +less_equal: ufunc +log10: ufunc +log1p: ufunc +log2: ufunc +log: ufunc +logaddexp2: ufunc +logaddexp: ufunc +logical_and: ufunc +logical_not: ufunc +logical_or: ufunc +logical_xor: ufunc +matmul: ufunc +maximum: ufunc +minimum: ufunc +modf: ufunc +multiply: ufunc +negative: ufunc +nextafter: ufunc +not_equal: ufunc +positive: ufunc +power: ufunc +rad2deg: ufunc +radians: ufunc +reciprocal: ufunc +remainder: ufunc +right_shift: ufunc +rint: ufunc +sign: ufunc +signbit: ufunc +sin: ufunc +sinh: ufunc +spacing: ufunc +sqrt: ufunc +square: ufunc +subtract: ufunc +tan: ufunc +tanh: ufunc +true_divide: ufunc +trunc: ufunc + +# Warnings +class ModuleDeprecationWarning(DeprecationWarning): ... +class VisibleDeprecationWarning(UserWarning): ... +class ComplexWarning(RuntimeWarning): ... +class RankWarning(UserWarning): ... + +# Errors +class TooHardError(RuntimeError): ... + +class AxisError(ValueError, IndexError): + def __init__( + self, axis: int, ndim: Optional[int] = ..., msg_prefix: Optional[str] = ... + ) -> None: ... + +# Functions from np.core.numerictypes +_DefaultType = TypeVar("_DefaultType") + +def maximum_sctype(t: DtypeLike) -> dtype: ... +def issctype(rep: object) -> bool: ... +@overload +def obj2sctype(rep: object) -> Optional[generic]: ... +@overload +def obj2sctype(rep: object, default: None) -> Optional[generic]: ... +@overload +def obj2sctype( + rep: object, default: Type[_DefaultType] +) -> Union[generic, Type[_DefaultType]]: ... +def issubclass_(arg1: object, arg2: Union[object, Tuple[object, ...]]) -> bool: ... +def issubsctype( + arg1: Union[ndarray, DtypeLike], arg2: Union[ndarray, DtypeLike] +) -> bool: ... +def issubdtype(arg1: DtypeLike, arg2: DtypeLike) -> bool: ... +def sctype2char(sctype: object) -> str: ... +def find_common_type( + array_types: Sequence[DtypeLike], scalar_types: Sequence[DtypeLike] +) -> dtype: ... + +# Functions from np.core.fromnumeric +_Mode = Literal["raise", "wrap", "clip"] +_Order = Literal["C", "F", "A"] +_PartitionKind = Literal["introselect"] +_SortKind = Literal["quicksort", "mergesort", "heapsort", "stable"] +_Side = Literal["left", "right"] + +# Various annotations for scalars + +# While dt.datetime and dt.timedelta are not technically part of NumPy, +# they are one of the rare few builtin scalars which serve as valid return types. +# See https://github.com/numpy/numpy-stubs/pull/67#discussion_r412604113. +_ScalarNumpy = Union[generic, dt.datetime, dt.timedelta] +_ScalarBuiltin = Union[str, bytes, dt.date, dt.timedelta, bool, int, float, complex] +_Scalar = Union[_ScalarBuiltin, _ScalarNumpy] + +# Integers and booleans can generally be used interchangeably +_ScalarIntOrBool = TypeVar("_ScalarIntOrBool", bound=Union[integer, bool_]) +_ScalarGeneric = TypeVar("_ScalarGeneric", bound=generic) +_ScalarGenericDT = TypeVar( + "_ScalarGenericDT", bound=Union[dt.datetime, dt.timedelta, generic] +) + +# An array-like object consisting of integers +_Int = Union[int, integer] +_Bool = Union[bool, bool_] +_IntOrBool = Union[_Int, _Bool] +_ArrayLikeIntNested = ArrayLike # TODO: wait for support for recursive types +_ArrayLikeBoolNested = ArrayLike # TODO: wait for support for recursive types + +# Integers and booleans can generally be used interchangeably +_ArrayLikeIntOrBool = Union[ + _IntOrBool, + ndarray, + Sequence[_IntOrBool], + Sequence[_ArrayLikeIntNested], + Sequence[_ArrayLikeBoolNested], +] + +# The signature of take() follows a common theme with its overloads: +# 1. A generic comes in; the same generic comes out +# 2. A scalar comes in; a generic comes out +# 3. An array-like object comes in; some keyword ensures that a generic comes out +# 4. An array-like object comes in; an ndarray or generic comes out +@overload +def take( + a: _ScalarGenericDT, + indices: int, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarGenericDT: ... +@overload +def take( + a: _Scalar, + indices: int, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarNumpy: ... +@overload +def take( + a: ArrayLike, + indices: int, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarNumpy: ... +@overload +def take( + a: ArrayLike, + indices: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> Union[_ScalarNumpy, ndarray]: ... +def reshape(a: ArrayLike, newshape: _ShapeLike, order: _Order = ...) -> ndarray: ... +@overload +def choose( + a: _ScalarIntOrBool, + choices: Union[Sequence[ArrayLike], ndarray], + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarIntOrBool: ... +@overload +def choose( + a: _IntOrBool, + choices: Union[Sequence[ArrayLike], ndarray], + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> Union[integer, bool_]: ... +@overload +def choose( + a: _ArrayLikeIntOrBool, + choices: Union[Sequence[ArrayLike], ndarray], + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> ndarray: ... +def repeat( + a: ArrayLike, repeats: _ArrayLikeIntOrBool, axis: Optional[int] = ... +) -> ndarray: ... +def put( + a: ndarray, ind: _ArrayLikeIntOrBool, v: ArrayLike, mode: _Mode = ... +) -> None: ... +def swapaxes( + a: Union[Sequence[ArrayLike], ndarray], axis1: int, axis2: int +) -> ndarray: ... +def transpose( + a: ArrayLike, axes: Union[None, Sequence[int], ndarray] = ... +) -> ndarray: ... +def partition( + a: ArrayLike, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +@overload +def argpartition( + a: generic, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> integer: ... +@overload +def argpartition( + a: _ScalarBuiltin, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +@overload +def argpartition( + a: ArrayLike, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +def sort( + a: Union[Sequence[ArrayLike], ndarray], + axis: Optional[int] = ..., + kind: Optional[_SortKind] = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +def argsort( + a: Union[Sequence[ArrayLike], ndarray], + axis: Optional[int] = ..., + kind: Optional[_SortKind] = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +@overload +def argmax( + a: Union[Sequence[ArrayLike], ndarray], + axis: None = ..., + out: Optional[ndarray] = ..., +) -> integer: ... +@overload +def argmax( + a: Union[Sequence[ArrayLike], ndarray], + axis: int = ..., + out: Optional[ndarray] = ..., +) -> Union[integer, ndarray]: ... +@overload +def argmin( + a: Union[Sequence[ArrayLike], ndarray], + axis: None = ..., + out: Optional[ndarray] = ..., +) -> integer: ... +@overload +def argmin( + a: Union[Sequence[ArrayLike], ndarray], + axis: int = ..., + out: Optional[ndarray] = ..., +) -> Union[integer, ndarray]: ... +@overload +def searchsorted( + a: Union[Sequence[ArrayLike], ndarray], + v: _Scalar, + side: _Side = ..., + sorter: Union[None, Sequence[_IntOrBool], ndarray] = ..., # 1D int array +) -> integer: ... +@overload +def searchsorted( + a: Union[Sequence[ArrayLike], ndarray], + v: ArrayLike, + side: _Side = ..., + sorter: Union[None, Sequence[_IntOrBool], ndarray] = ..., # 1D int array +) -> ndarray: ... +def resize(a: ArrayLike, new_shape: _ShapeLike) -> ndarray: ... +@overload +def squeeze(a: _ScalarGeneric, axis: Optional[_ShapeLike] = ...) -> _ScalarGeneric: ... +@overload +def squeeze(a: ArrayLike, axis: Optional[_ShapeLike] = ...) -> ndarray: ... +def diagonal( + a: Union[Sequence[Sequence[ArrayLike]], ndarray], # >= 2D array + offset: int = ..., + axis1: int = ..., + axis2: int = ..., +) -> ndarray: ... +def trace( + a: Union[Sequence[Sequence[ArrayLike]], ndarray], # >= 2D array + offset: int = ..., + axis1: int = ..., + axis2: int = ..., + dtype: DtypeLike = ..., + out: Optional[ndarray] = ..., +) -> Union[number, ndarray]: ... +def ravel(a: ArrayLike, order: _Order = ...) -> ndarray: ... +def nonzero(a: ArrayLike) -> Tuple[ndarray, ...]: ... +def shape(a: ArrayLike) -> _Shape: ... +def compress( + condition: Union[Sequence[_Bool], ndarray], # 1D bool array + a: ArrayLike, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., +) -> ndarray: ... diff --git a/numpy/core/_internal.pyi b/numpy/core/_internal.pyi new file mode 100644 index 000000000000..1b3889e51cfe --- /dev/null +++ b/numpy/core/_internal.pyi @@ -0,0 +1,18 @@ +from typing import Any + +# TODO: add better annotations when ctypes is stubbed out + +class _ctypes: + @property + def data(self) -> int: ... + @property + def shape(self) -> Any: ... + @property + def strides(self) -> Any: ... + def data_as(self, obj: Any) -> Any: ... + def shape_as(self, obj: Any) -> Any: ... + def strides_as(self, obj: Any) -> Any: ... + def get_data(self) -> int: ... + def get_shape(self) -> Any: ... + def get_strides(self) -> Any: ... + def get_as_parameter(self) -> Any: ... diff --git a/numpy/core/setup.py b/numpy/core/setup.py index 16bac4272325..5351b30bfd88 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -967,6 +967,7 @@ def generate_umath_c(ext, build_dir): config.add_subpackage('tests') config.add_data_dir('tests/data') config.add_data_dir('tests/examples') + config.add_data_files('*.pyi') config.make_svn_version_py() diff --git a/numpy/py.typed b/numpy/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/numpy/setup.py b/numpy/setup.py index 52db6a68bbd9..c6498d101560 100644 --- a/numpy/setup.py +++ b/numpy/setup.py @@ -18,6 +18,8 @@ def configuration(parent_package='',top_path=None): config.add_subpackage('random') config.add_subpackage('testing') config.add_data_dir('doc') + config.add_data_files('py.typed') + config.add_data_files('*.pyi') config.add_subpackage('tests') config.make_config_py() # installs __config__.py return config diff --git a/numpy/tests/setup.py b/numpy/tests/setup.py new file mode 100644 index 000000000000..f034cdf957b3 --- /dev/null +++ b/numpy/tests/setup.py @@ -0,0 +1,10 @@ +def configuration(parent_package='', top_path=None): + from numpy.distutils.misc_util import Configuration + config = Configuration('tests', parent_package, top_path) + config.add_data_dir('typing') + return config + + +if __name__ == '__main__': + from numpy.distutils.core import setup + setup(configuration=configuration) diff --git a/numpy/tests/test_typing.py b/numpy/tests/test_typing.py new file mode 100644 index 000000000000..04ea3c64d06b --- /dev/null +++ b/numpy/tests/test_typing.py @@ -0,0 +1,142 @@ +import importlib.util +import itertools +import os +import re +from collections import defaultdict + +import pytest +try: + from mypy import api +except ImportError: + NO_MYPY = True +else: + NO_MYPY = False + +TESTS_DIR = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "typing", +) +PASS_DIR = os.path.join(TESTS_DIR, "pass") +FAIL_DIR = os.path.join(TESTS_DIR, "fail") +REVEAL_DIR = os.path.join(TESTS_DIR, "reveal") +MYPY_INI = os.path.join(TESTS_DIR, "mypy.ini") +CACHE_DIR = os.path.join(TESTS_DIR, ".mypy_cache") + + +def get_test_cases(directory): + for root, _, files in os.walk(directory): + for fname in files: + if os.path.splitext(fname)[-1] == ".py": + fullpath = os.path.join(root, fname) + # Use relative path for nice py.test name + relpath = os.path.relpath(fullpath, start=directory) + + yield pytest.param( + fullpath, + # Manually specify a name for the test + id=relpath, + ) + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(PASS_DIR)) +def test_success(path): + stdout, stderr, exitcode = api.run([ + "--config-file", + MYPY_INI, + "--cache-dir", + CACHE_DIR, + path, + ]) + assert exitcode == 0, stdout + assert re.match(r"Success: no issues found in \d+ source files?", stdout.strip()) + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(FAIL_DIR)) +def test_fail(path): + stdout, stderr, exitcode = api.run([ + "--config-file", + MYPY_INI, + "--cache-dir", + CACHE_DIR, + path, + ]) + assert exitcode != 0 + + with open(path) as fin: + lines = fin.readlines() + + errors = defaultdict(lambda: "") + error_lines = stdout.rstrip("\n").split("\n") + assert re.match( + r"Found \d+ errors? in \d+ files? \(checked \d+ source files?\)", + error_lines[-1].strip(), + ) + for error_line in error_lines[:-1]: + error_line = error_line.strip() + if not error_line: + continue + + match = re.match( + r"^.+\.py:(?P\d+): (error|note): .+$", + error_line, + ) + if match is None: + raise ValueError(f"Unexpected error line format: {error_line}") + lineno = int(match.group('lineno')) + errors[lineno] += error_line + + for i, line in enumerate(lines): + lineno = i + 1 + if " E:" not in line and lineno not in errors: + continue + + target_line = lines[lineno - 1] + if "# E:" in target_line: + marker = target_line.split("# E:")[-1].strip() + assert lineno in errors, f'Extra error "{marker}"' + assert marker in errors[lineno] + else: + pytest.fail(f"Error {repr(errors[lineno])} not found") + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(REVEAL_DIR)) +def test_reveal(path): + stdout, stderr, exitcode = api.run([ + "--config-file", + MYPY_INI, + "--cache-dir", + CACHE_DIR, + path, + ]) + + with open(path) as fin: + lines = fin.readlines() + + for error_line in stdout.split("\n"): + error_line = error_line.strip() + if not error_line: + continue + + match = re.match( + r"^.+\.py:(?P\d+): note: .+$", + error_line, + ) + if match is None: + raise ValueError(f"Unexpected reveal line format: {error_line}") + lineno = int(match.group('lineno')) + assert "Revealed type is" in error_line + marker = lines[lineno - 1].split("# E:")[-1].strip() + assert marker in error_line + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(PASS_DIR)) +def test_code_runs(path): + path_without_extension, _ = os.path.splitext(path) + dirname, filename = path.split(os.sep)[-2:] + spec = importlib.util.spec_from_file_location(f"{dirname}.{filename}", path) + test_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(test_module) diff --git a/numpy/tests/typing/fail/array_like.py b/numpy/tests/typing/fail/array_like.py new file mode 100644 index 000000000000..a5ef5795f3de --- /dev/null +++ b/numpy/tests/typing/fail/array_like.py @@ -0,0 +1,22 @@ +from typing import Any, TYPE_CHECKING + +import numpy as np + +if TYPE_CHECKING: + from numpy.typing import ArrayLike +else: + ArrayLike = Any + + +class A: + pass + + +x1: ArrayLike = (i for i in range(10)) # E: Incompatible types in assignment +x2: ArrayLike = A() # E: Incompatible types in assignment +x3: ArrayLike = {1: "foo", 2: "bar"} # E: Incompatible types in assignment + +scalar = np.int64(1) +scalar.__array__(dtype=np.float64) # E: Unexpected keyword argument +array = np.array([1]) +array.__array__(dtype=np.float64) # E: Unexpected keyword argument diff --git a/numpy/tests/typing/fail/fromnumeric.py b/numpy/tests/typing/fail/fromnumeric.py new file mode 100644 index 000000000000..f158a107176b --- /dev/null +++ b/numpy/tests/typing/fail/fromnumeric.py @@ -0,0 +1,101 @@ +"""Tests for :mod:`numpy.core.fromnumeric`.""" + +import numpy as np + +A = np.array(True, ndmin=2, dtype=bool) +A.setflags(write=False) + +a = np.bool_(True) + +np.take(a, None) # E: No overload variant of "take" matches argument type +np.take(a, axis=1.0) # E: No overload variant of "take" matches argument type +np.take(A, out=1) # E: No overload variant of "take" matches argument type +np.take(A, mode="bob") # E: No overload variant of "take" matches argument type + +np.reshape(a, None) # E: Argument 2 to "reshape" has incompatible type +np.reshape(A, 1, order="bob") # E: Argument "order" to "reshape" has incompatible type + +np.choose(a, None) # E: No overload variant of "choose" matches argument type +np.choose(a, out=1.0) # E: No overload variant of "choose" matches argument type +np.choose(A, mode="bob") # E: No overload variant of "choose" matches argument type + +np.repeat(a, None) # E: Argument 2 to "repeat" has incompatible type +np.repeat(A, 1, axis=1.0) # E: Argument "axis" to "repeat" has incompatible type + +np.swapaxes(a, 0, 0) # E: Argument 1 to "swapaxes" has incompatible type +np.swapaxes(A, None, 1) # E: Argument 2 to "swapaxes" has incompatible type +np.swapaxes(A, 1, [0]) # E: Argument 3 to "swapaxes" has incompatible type + +np.transpose(a, axes=1) # E: Argument "axes" to "transpose" has incompatible type +np.transpose(A, axes=1.0) # E: Argument "axes" to "transpose" has incompatible type + +np.partition(a, None) # E: Argument 2 to "partition" has incompatible type +np.partition( + a, 0, axis="bob" # E: Argument "axis" to "partition" has incompatible type +) +np.partition( + A, 0, kind="bob" # E: Argument "kind" to "partition" has incompatible type +) +np.partition( + A, 0, order=range(5) # E: Argument "order" to "partition" has incompatible type +) + +np.argpartition( # E: No overload variant of "argpartition" matches argument type + a, None +) +np.argpartition( # E: No overload variant of "argpartition" matches argument type + a, 0, axis="bob" +) +np.argpartition( # E: No overload variant of "argpartition" matches argument type + A, 0, kind="bob" +) +np.argpartition( + A, 0, order=range(5) # E: Argument "order" to "argpartition" has incompatible type +) + +np.sort(a) # E: Argument 1 to "sort" has incompatible type +np.sort(A, axis="bob") # E: Argument "axis" to "sort" has incompatible type +np.sort(A, kind="bob") # E: Argument "kind" to "sort" has incompatible type +np.sort(A, order=range(5)) # E: Argument "order" to "sort" has incompatible type + +np.argsort(a) # E: Argument 1 to "argsort" has incompatible type +np.argsort(A, axis="bob") # E: Argument "axis" to "argsort" has incompatible type +np.argsort(A, kind="bob") # E: Argument "kind" to "argsort" has incompatible type +np.argsort(A, order=range(5)) # E: Argument "order" to "argsort" has incompatible type + +np.argmax(a) # E: No overload variant of "argmax" matches argument type +np.argmax(A, axis="bob") # E: No overload variant of "argmax" matches argument type +np.argmax(A, kind="bob") # E: No overload variant of "argmax" matches argument type + +np.argmin(a) # E: No overload variant of "argmin" matches argument type +np.argmin(A, axis="bob") # E: No overload variant of "argmin" matches argument type +np.argmin(A, kind="bob") # E: No overload variant of "argmin" matches argument type + +np.searchsorted(a, 0) # E: No overload variant of "searchsorted" matches argument type +np.searchsorted( # E: No overload variant of "searchsorted" matches argument type + A[0], 0, side="bob" +) +np.searchsorted( # E: No overload variant of "searchsorted" matches argument type + A[0], 0, sorter=1.0 +) + +np.resize(A, 1.0) # E: Argument 2 to "resize" has incompatible type + +np.squeeze(A, 1.0) # E: No overload variant of "squeeze" matches argument type + +np.diagonal(a) # E: Argument 1 to "diagonal" has incompatible type +np.diagonal(A, offset=None) # E: Argument "offset" to "diagonal" has incompatible type +np.diagonal(A, axis1="bob") # E: Argument "axis1" to "diagonal" has incompatible type +np.diagonal(A, axis2=[]) # E: Argument "axis2" to "diagonal" has incompatible type + +np.trace(a) # E: Argument 1 to "trace" has incompatible type +np.trace(A, offset=None) # E: Argument "offset" to "trace" has incompatible type +np.trace(A, axis1="bob") # E: Argument "axis1" to "trace" has incompatible type +np.trace(A, axis2=[]) # E: Argument "axis2" to "trace" has incompatible type + +np.ravel(a, order="bob") # E: Argument "order" to "ravel" has incompatible type + +np.compress(True, A) # E: Argument 1 to "compress" has incompatible type +np.compress( + [True], A, axis=1.0 # E: Argument "axis" to "compress" has incompatible type +) diff --git a/numpy/tests/typing/fail/ndarray.py b/numpy/tests/typing/fail/ndarray.py new file mode 100644 index 000000000000..5a5130d40649 --- /dev/null +++ b/numpy/tests/typing/fail/ndarray.py @@ -0,0 +1,11 @@ +import numpy as np + +# Ban setting dtype since mutating the type of the array in place +# makes having ndarray be generic over dtype impossible. Generally +# users should use `ndarray.view` in this situation anyway. See +# +# https://github.com/numpy/numpy-stubs/issues/7 +# +# for more context. +float_array = np.array([1.0]) +float_array.dtype = np.bool_ # E: Property "dtype" defined in "ndarray" is read-only diff --git a/numpy/tests/typing/fail/numerictypes.py b/numpy/tests/typing/fail/numerictypes.py new file mode 100644 index 000000000000..dd03eacc1c97 --- /dev/null +++ b/numpy/tests/typing/fail/numerictypes.py @@ -0,0 +1,13 @@ +import numpy as np + +# Techincally this works, but probably shouldn't. See +# +# https://github.com/numpy/numpy/issues/16366 +# +np.maximum_sctype(1) # E: incompatible type "int" + +np.issubsctype(1, np.int64) # E: incompatible type "int" + +np.issubdtype(1, np.int64) # E: incompatible type "int" + +np.find_common_type(np.int64, np.int64) # E: incompatible type "Type[int64]" diff --git a/numpy/tests/typing/fail/scalars.py b/numpy/tests/typing/fail/scalars.py new file mode 100644 index 000000000000..0dfc551243c8 --- /dev/null +++ b/numpy/tests/typing/fail/scalars.py @@ -0,0 +1,67 @@ +import numpy as np + +# Construction + +np.float32(3j) # E: incompatible type + +# Technically the following examples are valid NumPy code. But they +# are not considered a best practice, and people who wish to use the +# stubs should instead do +# +# np.array([1.0, 0.0, 0.0], dtype=np.float32) +# np.array([], dtype=np.complex64) +# +# See e.g. the discussion on the mailing list +# +# https://mail.python.org/pipermail/numpy-discussion/2020-April/080566.html +# +# and the issue +# +# https://github.com/numpy/numpy-stubs/issues/41 +# +# for more context. +np.float32([1.0, 0.0, 0.0]) # E: incompatible type +np.complex64([]) # E: incompatible type + +np.complex64(1, 2) # E: Too many arguments +# TODO: protocols (can't check for non-existent protocols w/ __getattr__) + +np.datetime64(0) # E: non-matching overload + +dt_64 = np.datetime64(0, "D") +td_64 = np.timedelta64(1, "h") + +dt_64 + dt_64 # E: Unsupported operand types + +td_64 - dt_64 # E: Unsupported operand types +td_64 / dt_64 # E: No overload +td_64 % 1 # E: Unsupported operand types +td_64 % dt_64 # E: Unsupported operand types + + +class A: + def __float__(self): + return 1.0 + + +np.int8(A()) # E: incompatible type +np.int16(A()) # E: incompatible type +np.int32(A()) # E: incompatible type +np.int64(A()) # E: incompatible type +np.uint8(A()) # E: incompatible type +np.uint16(A()) # E: incompatible type +np.uint32(A()) # E: incompatible type +np.uint64(A()) # E: incompatible type + +np.void("test") # E: incompatible type + +np.generic(1) # E: Cannot instantiate abstract class +np.number(1) # E: Cannot instantiate abstract class +np.integer(1) # E: Cannot instantiate abstract class +np.signedinteger(1) # E: Cannot instantiate abstract class +np.unsignedinteger(1) # E: Cannot instantiate abstract class +np.inexact(1) # E: Cannot instantiate abstract class +np.floating(1) # E: Cannot instantiate abstract class +np.complexfloating(1) # E: Cannot instantiate abstract class +np.character("test") # E: Cannot instantiate abstract class +np.flexible(b"test") # E: Cannot instantiate abstract class diff --git a/numpy/tests/typing/fail/simple.py b/numpy/tests/typing/fail/simple.py new file mode 100644 index 000000000000..b5e9d1b13097 --- /dev/null +++ b/numpy/tests/typing/fail/simple.py @@ -0,0 +1,10 @@ +"""Simple expression that should fail with mypy.""" + +import numpy as np + +# Array creation routines checks +np.zeros("test") # E: incompatible type +np.zeros() # E: Too few arguments + +np.ones("test") # E: incompatible type +np.ones() # E: Too few arguments diff --git a/numpy/tests/typing/fail/ufuncs.py b/numpy/tests/typing/fail/ufuncs.py new file mode 100644 index 000000000000..b178a4ea4a6e --- /dev/null +++ b/numpy/tests/typing/fail/ufuncs.py @@ -0,0 +1,5 @@ +import numpy as np + +np.sin.nin + "foo" # E: Unsupported operand types +np.sin(1, foo="bar") # E: Unexpected keyword argument +np.sin(1, extobj=["foo", "foo", "foo"]) # E: incompatible type diff --git a/numpy/tests/typing/fail/warnings_and_errors.py b/numpy/tests/typing/fail/warnings_and_errors.py new file mode 100644 index 000000000000..7390cc45f201 --- /dev/null +++ b/numpy/tests/typing/fail/warnings_and_errors.py @@ -0,0 +1,7 @@ +import numpy as np + +np.AxisError(1.0) # E: Argument 1 to "AxisError" has incompatible type +np.AxisError(1, ndim=2.0) # E: Argument "ndim" to "AxisError" has incompatible type +np.AxisError( + 2, msg_prefix=404 # E: Argument "msg_prefix" to "AxisError" has incompatible type +) diff --git a/numpy/tests/typing/mypy.ini b/numpy/tests/typing/mypy.ini new file mode 100644 index 000000000000..91d93588af15 --- /dev/null +++ b/numpy/tests/typing/mypy.ini @@ -0,0 +1,8 @@ +[mypy] +mypy_path = ../../.. + +[mypy-numpy] +ignore_errors = True + +[mypy-numpy.*] +ignore_errors = True diff --git a/numpy/tests/typing/pass/array_like.py b/numpy/tests/typing/pass/array_like.py new file mode 100644 index 000000000000..098149c4b7e3 --- /dev/null +++ b/numpy/tests/typing/pass/array_like.py @@ -0,0 +1,44 @@ +from typing import Any, List, Optional, TYPE_CHECKING + +import numpy as np + +if TYPE_CHECKING: + from numpy.typing import ArrayLike, DtypeLike, _SupportsArray +else: + ArrayLike = Any + DtypeLike = Any + _SupportsArray = Any + +x1: ArrayLike = True +x2: ArrayLike = 5 +x3: ArrayLike = 1.0 +x4: ArrayLike = 1 + 1j +x5: ArrayLike = np.int8(1) +x6: ArrayLike = np.float64(1) +x7: ArrayLike = np.complex128(1) +x8: ArrayLike = np.array([1, 2, 3]) +x9: ArrayLike = [1, 2, 3] +x10: ArrayLike = (1, 2, 3) +x11: ArrayLike = "foo" + + +class A: + def __array__(self, dtype: DtypeLike = None) -> np.ndarray: + return np.array([1, 2, 3]) + + +x12: ArrayLike = A() + +scalar: _SupportsArray = np.int64(1) +scalar.__array__(np.float64) +array: _SupportsArray = np.array(1) +array.__array__(np.float64) + +a: _SupportsArray = A() +a.__array__(np.int64) +a.__array__(dtype=np.int64) + +# Escape hatch for when you mean to make something like an object +# array. +object_array_scalar: Any = (i for i in range(10)) +np.array(object_array_scalar) diff --git a/numpy/tests/typing/pass/fromnumeric.py b/numpy/tests/typing/pass/fromnumeric.py new file mode 100644 index 000000000000..0ce8ef97050b --- /dev/null +++ b/numpy/tests/typing/pass/fromnumeric.py @@ -0,0 +1,116 @@ +"""Tests for :mod:`numpy.core.fromnumeric`.""" + +import numpy as np + +A = np.array(True, ndmin=2, dtype=bool) +B = np.array(1.0, ndmin=2, dtype=np.float32) +A.setflags(write=False) +B.setflags(write=False) + +a = np.bool_(True) +b = np.float32(1.0) +c = 1.0 + +np.take(a, 0) +np.take(b, 0) +np.take(c, 0) +np.take(A, 0) +np.take(B, 0) +np.take(A, [0]) +np.take(B, [0]) + +np.reshape(a, 1) +np.reshape(b, 1) +np.reshape(c, 1) +np.reshape(A, 1) +np.reshape(B, 1) + +np.choose(a, [True, True]) +np.choose(A, [1.0, 1.0]) + +np.repeat(a, 1) +np.repeat(b, 1) +np.repeat(c, 1) +np.repeat(A, 1) +np.repeat(B, 1) + +np.swapaxes(A, 0, 0) +np.swapaxes(B, 0, 0) + +np.transpose(a) +np.transpose(b) +np.transpose(c) +np.transpose(A) +np.transpose(B) + +np.partition(a, 0, axis=None) +np.partition(b, 0, axis=None) +np.partition(c, 0, axis=None) +np.partition(A, 0) +np.partition(B, 0) + +np.argpartition(a, 0) +np.argpartition(b, 0) +np.argpartition(c, 0) +np.argpartition(A, 0) +np.argpartition(B, 0) + +np.sort(A, 0) +np.sort(B, 0) + +np.argsort(A, 0) +np.argsort(B, 0) + +np.argmax(A) +np.argmax(B) +np.argmax(A, axis=0) +np.argmax(B, axis=0) + +np.argmin(A) +np.argmin(B) +np.argmin(A, axis=0) +np.argmin(B, axis=0) + +np.searchsorted(A[0], 0) +np.searchsorted(B[0], 0) +np.searchsorted(A[0], [0]) +np.searchsorted(B[0], [0]) + +np.resize(a, (5, 5)) +np.resize(b, (5, 5)) +np.resize(c, (5, 5)) +np.resize(A, (5, 5)) +np.resize(B, (5, 5)) + +np.squeeze(a) +np.squeeze(b) +np.squeeze(c) +np.squeeze(A) +np.squeeze(B) + +np.diagonal(A) +np.diagonal(B) + +np.trace(A) +np.trace(B) + +np.ravel(a) +np.ravel(b) +np.ravel(c) +np.ravel(A) +np.ravel(B) + +np.nonzero(A) +np.nonzero(B) + +np.shape(a) +np.shape(b) +np.shape(c) +np.shape(A) +np.shape(B) + +np.compress([True], a) +np.compress([True], b) +np.compress([True], c) +np.compress([True], A) +np.compress([True], B) diff --git a/numpy/tests/typing/pass/ndarray_conversion.py b/numpy/tests/typing/pass/ndarray_conversion.py new file mode 100644 index 000000000000..303cf53e4453 --- /dev/null +++ b/numpy/tests/typing/pass/ndarray_conversion.py @@ -0,0 +1,94 @@ +import os +import tempfile + +import numpy as np + +nd = np.array([[1, 2], [3, 4]]) +scalar_array = np.array(1) + +# item +scalar_array.item() +nd.item(1) +nd.item(0, 1) +nd.item((0, 1)) + +# tolist is pretty simple + +# itemset +scalar_array.itemset(3) +nd.itemset(3, 0) +nd.itemset((0, 0), 3) + +# tobytes +nd.tobytes() +nd.tobytes("C") +nd.tobytes(None) + +# tofile +if os.name != "nt": + with tempfile.NamedTemporaryFile(suffix=".txt") as tmp: + nd.tofile(tmp.name) + nd.tofile(tmp.name, "") + nd.tofile(tmp.name, sep="") + + nd.tofile(tmp.name, "", "%s") + nd.tofile(tmp.name, format="%s") + + nd.tofile(tmp) + +# dump is pretty simple +# dumps is pretty simple + +# astype +nd.astype("float") +nd.astype(float) + +nd.astype(float, "K") +nd.astype(float, order="K") + +nd.astype(float, "K", "unsafe") +nd.astype(float, casting="unsafe") + +nd.astype(float, "K", "unsafe", True) +nd.astype(float, subok=True) + +nd.astype(float, "K", "unsafe", True, True) +nd.astype(float, copy=True) + +# byteswap +nd.byteswap() +nd.byteswap(True) + +# copy +nd.copy() +nd.copy("C") + +# view +nd.view() +nd.view(np.int64) +nd.view(dtype=np.int64) +nd.view(np.int64, np.matrix) +nd.view(type=np.matrix) + +# getfield +complex_array = np.array([[1 + 1j, 0], [0, 1 - 1j]], dtype=np.complex128) + +complex_array.getfield("float") +complex_array.getfield(float) + +complex_array.getfield("float", 8) +complex_array.getfield(float, offset=8) + +# setflags +nd.setflags() + +nd.setflags(True) +nd.setflags(write=True) + +nd.setflags(True, True) +nd.setflags(write=True, align=True) + +nd.setflags(True, True, False) +nd.setflags(write=True, align=True, uic=False) + +# fill is pretty simple diff --git a/numpy/tests/typing/pass/ndarray_shape_manipulation.py b/numpy/tests/typing/pass/ndarray_shape_manipulation.py new file mode 100644 index 000000000000..0ca3dff392e1 --- /dev/null +++ b/numpy/tests/typing/pass/ndarray_shape_manipulation.py @@ -0,0 +1,47 @@ +import numpy as np + +nd1 = np.array([[1, 2], [3, 4]]) + +# reshape +nd1.reshape(4) +nd1.reshape(2, 2) +nd1.reshape((2, 2)) + +nd1.reshape((2, 2), order="C") +nd1.reshape(4, order="C") + +# resize +nd1.resize() +nd1.resize(4) +nd1.resize(2, 2) +nd1.resize((2, 2)) + +nd1.resize((2, 2), refcheck=True) +nd1.resize(4, refcheck=True) + +nd2 = np.array([[1, 2], [3, 4]]) + +# transpose +nd2.transpose() +nd2.transpose(1, 0) +nd2.transpose((1, 0)) + +# swapaxes +nd2.swapaxes(0, 1) + +# flatten +nd2.flatten() +nd2.flatten("C") + +# ravel +nd2.ravel() +nd2.ravel("C") + +# squeeze +nd2.squeeze() + +nd3 = np.array([[1, 2]]) +nd3.squeeze(0) + +nd4 = np.array([[[1, 2]]]) +nd4.squeeze((0, 1)) diff --git a/numpy/tests/typing/pass/numerictypes.py b/numpy/tests/typing/pass/numerictypes.py new file mode 100644 index 000000000000..4f205cabcb0d --- /dev/null +++ b/numpy/tests/typing/pass/numerictypes.py @@ -0,0 +1,29 @@ +import numpy as np + +np.maximum_sctype("S8") +np.maximum_sctype(object) + +np.issctype(object) +np.issctype("S8") + +np.obj2sctype(list) +np.obj2sctype(list, default=None) +np.obj2sctype(list, default=np.string_) + +np.issubclass_(np.int32, int) +np.issubclass_(np.float64, float) +np.issubclass_(np.float64, (int, float)) + +np.issubsctype("int64", int) +np.issubsctype(np.array([1]), np.array([1])) + +np.issubdtype("S1", np.string_) +np.issubdtype(np.float64, np.float32) + +np.sctype2char("S1") +np.sctype2char(list) + +np.find_common_type([], [np.int64, np.float32, complex]) +np.find_common_type((), (np.int64, np.float32, complex)) +np.find_common_type([np.int64, np.float32], []) +np.find_common_type([np.float32], [np.int64, np.float64]) diff --git a/numpy/tests/typing/pass/scalars.py b/numpy/tests/typing/pass/scalars.py new file mode 100644 index 000000000000..bd055673b41b --- /dev/null +++ b/numpy/tests/typing/pass/scalars.py @@ -0,0 +1,88 @@ +import numpy as np + + +# Construction +class C: + def __complex__(self): + return 3j + + +class B: + def __int__(self): + return 4 + + +class A: + def __float__(self): + return 4.0 + + +np.complex64(3j) +np.complex64(C()) +np.complex128(3j) +np.complex128(C()) + +np.int8(4) +np.int16(3.4) +np.int32(4) +np.int64(-1) +np.uint8(B()) +np.uint32() + +np.float16(A()) +np.float32(16) +np.float64(3.0) + +np.bytes_(b"hello") +np.str_("hello") + +# Protocols +float(np.int8(4)) +int(np.int16(5)) +np.int8(np.float32(6)) + +# TODO(alan): test after https://github.com/python/typeshed/pull/2004 +# complex(np.int32(8)) + +abs(np.int8(4)) + +# Array-ish semantics +np.int8().real +np.int16().imag +np.int32().data +np.int64().flags + +np.uint8().itemsize * 2 +np.uint16().ndim + 1 +np.uint32().strides +np.uint64().shape + +# Time structures +np.datetime64() +np.datetime64(0, "D") +np.datetime64("2019") +np.datetime64("2019", "D") + +np.timedelta64() +np.timedelta64(0) +np.timedelta64(0, "D") + +dt_64 = np.datetime64(0, "D") +td_64 = np.timedelta64(1, "h") + +dt_64 + td_64 +dt_64 - dt_64 +dt_64 - td_64 + +td_64 + td_64 +td_64 - td_64 +td_64 / 1.0 +td_64 / td_64 +td_64 % td_64 + +np.void(1) +np.void(np.int64(1)) +np.void(True) +np.void(np.bool_(True)) +np.void(b"test") +np.void(np.bytes_("test")) diff --git a/numpy/tests/typing/pass/simple.py b/numpy/tests/typing/pass/simple.py new file mode 100644 index 000000000000..e0157ab81cd2 --- /dev/null +++ b/numpy/tests/typing/pass/simple.py @@ -0,0 +1,176 @@ +"""Simple expression that should pass with mypy.""" +import operator + +import numpy as np +from typing import Iterable # noqa: F401 + +# Basic checks +array = np.array([1, 2]) + + +def ndarray_func(x): + # type: (np.ndarray) -> np.ndarray + return x + + +ndarray_func(np.array([1, 2])) +array == 1 +array.dtype == float + +# Array creation routines checks +ndarray_func(np.zeros([1, 2])) +ndarray_func(np.ones([1, 2])) +ndarray_func(np.empty([1, 2])) + +ndarray_func(np.zeros_like(array)) +ndarray_func(np.ones_like(array)) +ndarray_func(np.empty_like(array)) + +# Dtype construction +np.dtype(float) +np.dtype(np.float64) +np.dtype(None) +np.dtype("float64") +np.dtype(np.dtype(float)) +np.dtype(("U", 10)) +np.dtype((np.int32, (2, 2))) +# Define the arguments on the previous line to prevent bidirectional +# type inference in mypy from broadening the types. +two_tuples_dtype = [("R", "u1"), ("G", "u1"), ("B", "u1")] +np.dtype(two_tuples_dtype) + +three_tuples_dtype = [("R", "u1", 2)] +np.dtype(three_tuples_dtype) + +mixed_tuples_dtype = [("R", "u1"), ("G", np.unicode_, 1)] +np.dtype(mixed_tuples_dtype) + +shape_tuple_dtype = [("R", "u1", (2, 2))] +np.dtype(shape_tuple_dtype) + +shape_like_dtype = [("R", "u1", (2, 2)), ("G", np.unicode_, 1)] +np.dtype(shape_like_dtype) + +object_dtype = [("field1", object)] +np.dtype(object_dtype) + +np.dtype({"col1": ("U10", 0), "col2": ("float32", 10)}) +np.dtype((np.int32, {"real": (np.int16, 0), "imag": (np.int16, 2)})) +np.dtype((np.int32, (np.int8, 4))) + +# Dtype comparision +np.dtype(float) == float +np.dtype(float) != np.float64 +np.dtype(float) < None +np.dtype(float) <= "float64" +np.dtype(float) > np.dtype(float) +np.dtype(float) >= np.dtype(("U", 10)) + +# Iteration and indexing +def iterable_func(x): + # type: (Iterable) -> Iterable + return x + + +iterable_func(array) +[element for element in array] +iter(array) +zip(array, array) +array[1] +array[:] +array[...] +array[:] = 0 + +array_2d = np.ones((3, 3)) +array_2d[:2, :2] +array_2d[..., 0] +array_2d[:2, :2] = 0 + +# Other special methods +len(array) +str(array) +array_scalar = np.array(1) +int(array_scalar) +float(array_scalar) +# currently does not work due to https://github.com/python/typeshed/issues/1904 +# complex(array_scalar) +bytes(array_scalar) +operator.index(array_scalar) +bool(array_scalar) + +# comparisons +array < 1 +array <= 1 +array == 1 +array != 1 +array > 1 +array >= 1 +1 < array +1 <= array +1 == array +1 != array +1 > array +1 >= array + +# binary arithmetic +array + 1 +1 + array +array += 1 + +array - 1 +1 - array +array -= 1 + +array * 1 +1 * array +array *= 1 + +nonzero_array = np.array([1, 2]) +array / 1 +1 / nonzero_array +float_array = np.array([1.0, 2.0]) +float_array /= 1 + +array // 1 +1 // nonzero_array +array //= 1 + +array % 1 +1 % nonzero_array +array %= 1 + +divmod(array, 1) +divmod(1, nonzero_array) + +array ** 1 +1 ** array +array **= 1 + +array << 1 +1 << array +array <<= 1 + +array >> 1 +1 >> array +array >>= 1 + +array & 1 +1 & array +array &= 1 + +array ^ 1 +1 ^ array +array ^= 1 + +array | 1 +1 | array +array |= 1 + +# unary arithmetic +-array ++array +abs(array) +~array + +# Other methods +np.array([1, 2]).transpose() diff --git a/numpy/tests/typing/pass/simple_py3.py b/numpy/tests/typing/pass/simple_py3.py new file mode 100644 index 000000000000..c05a1ce612ac --- /dev/null +++ b/numpy/tests/typing/pass/simple_py3.py @@ -0,0 +1,6 @@ +import numpy as np + +array = np.array([1, 2]) + +# The @ operator is not in python 2 +array @ array diff --git a/numpy/tests/typing/pass/ufuncs.py b/numpy/tests/typing/pass/ufuncs.py new file mode 100644 index 000000000000..c81ac48d1bb6 --- /dev/null +++ b/numpy/tests/typing/pass/ufuncs.py @@ -0,0 +1,11 @@ +import numpy as np + +np.sin(1) +np.sin([1, 2, 3]) +np.sin(1, out=np.empty(1)) +np.matmul(np.ones((2, 2, 2)), np.ones((2, 2, 2)), axes=[(0, 1), (0, 1), (0, 1)]) +np.sin(1, signature="D") +np.sin(1, extobj=[16, 1, lambda: None]) +np.sin(1) + np.sin(1) +np.sin.types[0] +np.sin.__name__ diff --git a/numpy/tests/typing/pass/warnings_and_errors.py b/numpy/tests/typing/pass/warnings_and_errors.py new file mode 100644 index 000000000000..5b6ec2626c0c --- /dev/null +++ b/numpy/tests/typing/pass/warnings_and_errors.py @@ -0,0 +1,7 @@ +import numpy as np + +np.AxisError(1) +np.AxisError(1, ndim=2) +np.AxisError(1, ndim=None) +np.AxisError(1, ndim=2, msg_prefix="error") +np.AxisError(1, ndim=2, msg_prefix=None) diff --git a/numpy/tests/typing/reveal/constants.py b/numpy/tests/typing/reveal/constants.py new file mode 100644 index 000000000000..8e00810bd7c4 --- /dev/null +++ b/numpy/tests/typing/reveal/constants.py @@ -0,0 +1,44 @@ +import numpy as np + +reveal_type(np.Inf) # E: float +reveal_type(np.Infinity) # E: float +reveal_type(np.NAN) # E: float +reveal_type(np.NINF) # E: float +reveal_type(np.NZERO) # E: float +reveal_type(np.NaN) # E: float +reveal_type(np.PINF) # E: float +reveal_type(np.PZERO) # E: float +reveal_type(np.e) # E: float +reveal_type(np.euler_gamma) # E: float +reveal_type(np.inf) # E: float +reveal_type(np.infty) # E: float +reveal_type(np.nan) # E: float +reveal_type(np.pi) # E: float + +reveal_type(np.ALLOW_THREADS) # E: int +reveal_type(np.BUFSIZE) # E: int +reveal_type(np.CLIP) # E: int +reveal_type(np.ERR_CALL) # E: int +reveal_type(np.ERR_DEFAULT) # E: int +reveal_type(np.ERR_IGNORE) # E: int +reveal_type(np.ERR_LOG) # E: int +reveal_type(np.ERR_PRINT) # E: int +reveal_type(np.ERR_RAISE) # E: int +reveal_type(np.ERR_WARN) # E: int +reveal_type(np.FLOATING_POINT_SUPPORT) # E: int +reveal_type(np.FPE_DIVIDEBYZERO) # E: int +reveal_type(np.FPE_INVALID) # E: int +reveal_type(np.FPE_OVERFLOW) # E: int +reveal_type(np.FPE_UNDERFLOW) # E: int +reveal_type(np.MAXDIMS) # E: int +reveal_type(np.MAY_SHARE_BOUNDS) # E: int +reveal_type(np.MAY_SHARE_EXACT) # E: int +reveal_type(np.RAISE) # E: int +reveal_type(np.SHIFT_DIVIDEBYZERO) # E: int +reveal_type(np.SHIFT_INVALID) # E: int +reveal_type(np.SHIFT_OVERFLOW) # E: int +reveal_type(np.SHIFT_UNDERFLOW) # E: int +reveal_type(np.UFUNC_BUFSIZE_DEFAULT) # E: int +reveal_type(np.WRAP) # E: int +reveal_type(np.little_endian) # E: int +reveal_type(np.tracemalloc_domain) # E: int diff --git a/numpy/tests/typing/reveal/fromnumeric.py b/numpy/tests/typing/reveal/fromnumeric.py new file mode 100644 index 000000000000..7d79d5fa9b25 --- /dev/null +++ b/numpy/tests/typing/reveal/fromnumeric.py @@ -0,0 +1,135 @@ +"""Tests for :mod:`numpy.core.fromnumeric`.""" + +import numpy as np + +A = np.array(True, ndmin=2, dtype=bool) +B = np.array(1.0, ndmin=2, dtype=np.float32) +A.setflags(write=False) +B.setflags(write=False) + +a = np.bool_(True) +b = np.float32(1.0) +c = 1.0 + +reveal_type(np.take(a, 0)) # E: numpy.bool_ +reveal_type(np.take(b, 0)) # E: numpy.float32 +reveal_type( + np.take(c, 0) # E: Union[numpy.generic, datetime.datetime, datetime.timedelta] +) +reveal_type( + np.take(A, 0) # E: Union[numpy.generic, datetime.datetime, datetime.timedelta] +) +reveal_type( + np.take(B, 0) # E: Union[numpy.generic, datetime.datetime, datetime.timedelta] +) +reveal_type( + np.take( # E: Union[Union[numpy.generic, datetime.datetime, datetime.timedelta], numpy.ndarray] + A, [0] + ) +) +reveal_type( + np.take( # E: Union[Union[numpy.generic, datetime.datetime, datetime.timedelta], numpy.ndarray] + B, [0] + ) +) + +reveal_type(np.reshape(a, 1)) # E: numpy.ndarray +reveal_type(np.reshape(b, 1)) # E: numpy.ndarray +reveal_type(np.reshape(c, 1)) # E: numpy.ndarray +reveal_type(np.reshape(A, 1)) # E: numpy.ndarray +reveal_type(np.reshape(B, 1)) # E: numpy.ndarray + +reveal_type(np.choose(a, [True, True])) # E: numpy.bool_ +reveal_type(np.choose(A, [True, True])) # E: numpy.ndarray + +reveal_type(np.repeat(a, 1)) # E: numpy.ndarray +reveal_type(np.repeat(b, 1)) # E: numpy.ndarray +reveal_type(np.repeat(c, 1)) # E: numpy.ndarray +reveal_type(np.repeat(A, 1)) # E: numpy.ndarray +reveal_type(np.repeat(B, 1)) # E: numpy.ndarray + +# TODO: Add tests for np.put() + +reveal_type(np.swapaxes(A, 0, 0)) # E: numpy.ndarray +reveal_type(np.swapaxes(B, 0, 0)) # E: numpy.ndarray + +reveal_type(np.transpose(a)) # E: numpy.ndarray +reveal_type(np.transpose(b)) # E: numpy.ndarray +reveal_type(np.transpose(c)) # E: numpy.ndarray +reveal_type(np.transpose(A)) # E: numpy.ndarray +reveal_type(np.transpose(B)) # E: numpy.ndarray + +reveal_type(np.partition(a, 0, axis=None)) # E: numpy.ndarray +reveal_type(np.partition(b, 0, axis=None)) # E: numpy.ndarray +reveal_type(np.partition(c, 0, axis=None)) # E: numpy.ndarray +reveal_type(np.partition(A, 0)) # E: numpy.ndarray +reveal_type(np.partition(B, 0)) # E: numpy.ndarray + +reveal_type(np.argpartition(a, 0)) # E: numpy.integer +reveal_type(np.argpartition(b, 0)) # E: numpy.integer +reveal_type(np.argpartition(c, 0)) # E: numpy.ndarray +reveal_type(np.argpartition(A, 0)) # E: numpy.ndarray +reveal_type(np.argpartition(B, 0)) # E: numpy.ndarray + +reveal_type(np.sort(A, 0)) # E: numpy.ndarray +reveal_type(np.sort(B, 0)) # E: numpy.ndarray + +reveal_type(np.argsort(A, 0)) # E: numpy.ndarray +reveal_type(np.argsort(B, 0)) # E: numpy.ndarray + +reveal_type(np.argmax(A)) # E: numpy.integer +reveal_type(np.argmax(B)) # E: numpy.integer +reveal_type(np.argmax(A, axis=0)) # E: Union[numpy.integer, numpy.ndarray] +reveal_type(np.argmax(B, axis=0)) # E: Union[numpy.integer, numpy.ndarray] + +reveal_type(np.argmin(A)) # E: numpy.integer +reveal_type(np.argmin(B)) # E: numpy.integer +reveal_type(np.argmin(A, axis=0)) # E: Union[numpy.integer, numpy.ndarray] +reveal_type(np.argmin(B, axis=0)) # E: Union[numpy.integer, numpy.ndarray] + +reveal_type(np.searchsorted(A[0], 0)) # E: numpy.integer +reveal_type(np.searchsorted(B[0], 0)) # E: numpy.integer +reveal_type(np.searchsorted(A[0], [0])) # E: numpy.ndarray +reveal_type(np.searchsorted(B[0], [0])) # E: numpy.ndarray + +reveal_type(np.resize(a, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(b, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(c, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(A, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(B, (5, 5))) # E: numpy.ndarray + +reveal_type(np.squeeze(a)) # E: numpy.bool_ +reveal_type(np.squeeze(b)) # E: numpy.float32 +reveal_type(np.squeeze(c)) # E: numpy.ndarray +reveal_type(np.squeeze(A)) # E: numpy.ndarray +reveal_type(np.squeeze(B)) # E: numpy.ndarray + +reveal_type(np.diagonal(A)) # E: numpy.ndarray +reveal_type(np.diagonal(B)) # E: numpy.ndarray + +reveal_type(np.trace(A)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.trace(B)) # E: Union[numpy.number, numpy.ndarray] + +reveal_type(np.ravel(a)) # E: numpy.ndarray +reveal_type(np.ravel(b)) # E: numpy.ndarray +reveal_type(np.ravel(c)) # E: numpy.ndarray +reveal_type(np.ravel(A)) # E: numpy.ndarray +reveal_type(np.ravel(B)) # E: numpy.ndarray + +reveal_type(np.nonzero(a)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(b)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(c)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(A)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(B)) # E: tuple[numpy.ndarray] + +reveal_type(np.shape(a)) # E: tuple[builtins.int] +reveal_type(np.shape(b)) # E: tuple[builtins.int] +reveal_type(np.shape(c)) # E: tuple[builtins.int] +reveal_type(np.shape(A)) # E: tuple[builtins.int] +reveal_type(np.shape(B)) # E: tuple[builtins.int] + +reveal_type(np.compress([True], a)) # E: numpy.ndarray +reveal_type(np.compress([True], b)) # E: numpy.ndarray +reveal_type(np.compress([True], c)) # E: numpy.ndarray +reveal_type(np.compress([True], A)) # E: numpy.ndarray +reveal_type(np.compress([True], B)) # E: numpy.ndarray diff --git a/numpy/tests/typing/reveal/ndarray_conversion.py b/numpy/tests/typing/reveal/ndarray_conversion.py new file mode 100644 index 000000000000..411adcf63197 --- /dev/null +++ b/numpy/tests/typing/reveal/ndarray_conversion.py @@ -0,0 +1,54 @@ +import numpy as np + +nd = np.array([[1, 2], [3, 4]]) + +# item +reveal_type(nd.item()) # E: Any +reveal_type(nd.item(1)) # E: Any +reveal_type(nd.item(0, 1)) # E: Any +reveal_type(nd.item((0, 1))) # E: Any + +# tolist +reveal_type(nd.tolist()) # E: builtins.list[Any] + +# itemset does not return a value +# tostring is pretty simple +# tobytes is pretty simple +# tofile does not return a value +# dump does not return a value +# dumps is pretty simple + +# astype +reveal_type(nd.astype("float")) # E: numpy.ndarray +reveal_type(nd.astype(float)) # E: numpy.ndarray +reveal_type(nd.astype(float, "K")) # E: numpy.ndarray +reveal_type(nd.astype(float, "K", "unsafe")) # E: numpy.ndarray +reveal_type(nd.astype(float, "K", "unsafe", True)) # E: numpy.ndarray +reveal_type(nd.astype(float, "K", "unsafe", True, True)) # E: numpy.ndarray + +# byteswap +reveal_type(nd.byteswap()) # E: numpy.ndarray +reveal_type(nd.byteswap(True)) # E: numpy.ndarray + +# copy +reveal_type(nd.copy()) # E: numpy.ndarray +reveal_type(nd.copy("C")) # E: numpy.ndarray + +# view +class SubArray(np.ndarray): + pass + + +reveal_type(nd.view()) # E: numpy.ndarray +reveal_type(nd.view(np.int64)) # E: numpy.ndarray +# replace `Any` with `numpy.matrix` when `matrix` will be added to stubs +reveal_type(nd.view(np.int64, np.matrix)) # E: Any +reveal_type(nd.view(np.int64, SubArray)) # E: SubArray + +# getfield +reveal_type(nd.getfield("float")) # E: numpy.ndarray +reveal_type(nd.getfield(float)) # E: numpy.ndarray +reveal_type(nd.getfield(float, 8)) # E: numpy.ndarray + +# setflags does not return a value +# fill does not return a value diff --git a/numpy/tests/typing/reveal/ndarray_shape_manipulation.py b/numpy/tests/typing/reveal/ndarray_shape_manipulation.py new file mode 100644 index 000000000000..a44e1cfa1aec --- /dev/null +++ b/numpy/tests/typing/reveal/ndarray_shape_manipulation.py @@ -0,0 +1,35 @@ +import numpy as np + +nd = np.array([[1, 2], [3, 4]]) + +# reshape +reveal_type(nd.reshape()) # E: numpy.ndarray +reveal_type(nd.reshape(4)) # E: numpy.ndarray +reveal_type(nd.reshape(2, 2)) # E: numpy.ndarray +reveal_type(nd.reshape((2, 2))) # E: numpy.ndarray + +reveal_type(nd.reshape((2, 2), order="C")) # E: numpy.ndarray +reveal_type(nd.reshape(4, order="C")) # E: numpy.ndarray + +# resize does not return a value + +# transpose +reveal_type(nd.transpose()) # E: numpy.ndarray +reveal_type(nd.transpose(1, 0)) # E: numpy.ndarray +reveal_type(nd.transpose((1, 0))) # E: numpy.ndarray + +# swapaxes +reveal_type(nd.swapaxes(0, 1)) # E: numpy.ndarray + +# flatten +reveal_type(nd.flatten()) # E: numpy.ndarray +reveal_type(nd.flatten("C")) # E: numpy.ndarray + +# ravel +reveal_type(nd.ravel()) # E: numpy.ndarray +reveal_type(nd.ravel("C")) # E: numpy.ndarray + +# squeeze +reveal_type(nd.squeeze()) # E: numpy.ndarray +reveal_type(nd.squeeze(0)) # E: numpy.ndarray +reveal_type(nd.squeeze((0, 2))) # E: numpy.ndarray diff --git a/numpy/tests/typing/reveal/numerictypes.py b/numpy/tests/typing/reveal/numerictypes.py new file mode 100644 index 000000000000..e026158cd14f --- /dev/null +++ b/numpy/tests/typing/reveal/numerictypes.py @@ -0,0 +1,18 @@ +import numpy as np + +reveal_type(np.issctype(np.generic)) # E: bool +reveal_type(np.issctype("foo")) # E: bool + +reveal_type(np.obj2sctype("S8")) # E: Union[numpy.generic, None] +reveal_type(np.obj2sctype("S8", default=None)) # E: Union[numpy.generic, None] +reveal_type( + np.obj2sctype("foo", default=int) # E: Union[numpy.generic, Type[builtins.int*]] +) + +reveal_type(np.issubclass_(np.float64, float)) # E: bool +reveal_type(np.issubclass_(np.float64, (int, float))) # E: bool + +reveal_type(np.sctype2char("S8")) # E: str +reveal_type(np.sctype2char(list)) # E: str + +reveal_type(np.find_common_type([np.int64], [np.int64])) # E: numpy.dtype diff --git a/numpy/tests/typing/reveal/scalars.py b/numpy/tests/typing/reveal/scalars.py new file mode 100644 index 000000000000..8a9555fc37c1 --- /dev/null +++ b/numpy/tests/typing/reveal/scalars.py @@ -0,0 +1,30 @@ +import numpy as np + +x = np.complex64(3 + 2j) + +reveal_type(x.real) # E: numpy.float32 +reveal_type(x.imag) # E: numpy.float32 + +reveal_type(x.real.real) # E: numpy.float32 +reveal_type(x.real.imag) # E: numpy.float32 + +reveal_type(x.itemsize) # E: int +reveal_type(x.shape) # E: tuple[builtins.int] +reveal_type(x.strides) # E: tuple[builtins.int] + +# Time structures +dt = np.datetime64(0, "D") +td = np.timedelta64(0, "D") + +reveal_type(dt + td) # E: numpy.datetime64 +reveal_type(dt + 1) # E: numpy.datetime64 +reveal_type(dt - dt) # E: numpy.timedelta64 +reveal_type(dt - 1) # E: numpy.timedelta64 + +reveal_type(td + td) # E: numpy.timedelta64 +reveal_type(td + 1) # E: numpy.timedelta64 +reveal_type(td - td) # E: numpy.timedelta64 +reveal_type(td - 1) # E: numpy.timedelta64 +reveal_type(td / 1.0) # E: numpy.timedelta64 +reveal_type(td / td) # E: float +reveal_type(td % td) # E: numpy.timedelta64 diff --git a/numpy/tests/typing/reveal/warnings_and_errors.py b/numpy/tests/typing/reveal/warnings_and_errors.py new file mode 100644 index 000000000000..c428deb7a164 --- /dev/null +++ b/numpy/tests/typing/reveal/warnings_and_errors.py @@ -0,0 +1,10 @@ +from typing import Type + +import numpy as np + +reveal_type(np.ModuleDeprecationWarning()) # E: numpy.ModuleDeprecationWarning +reveal_type(np.VisibleDeprecationWarning()) # E: numpy.VisibleDeprecationWarning +reveal_type(np.ComplexWarning()) # E: numpy.ComplexWarning +reveal_type(np.RankWarning()) # E: numpy.RankWarning +reveal_type(np.TooHardError()) # E: numpy.TooHardError +reveal_type(np.AxisError(1)) # E: numpy.AxisError diff --git a/numpy/typing.pyi b/numpy/typing.pyi new file mode 100644 index 000000000000..f5705192a851 --- /dev/null +++ b/numpy/typing.pyi @@ -0,0 +1,64 @@ +import sys +from typing import Any, Dict, List, overload, Sequence, Text, Tuple, Union + +from numpy import dtype, ndarray + +if sys.version_info >= (3, 8): + from typing import Protocol +else: + from typing_extensions import Protocol + +_Shape = Tuple[int, ...] + +# Anything that can be coerced to a shape tuple +_ShapeLike = Union[int, Sequence[int]] + +_DtypeLikeNested = Any # TODO: wait for support for recursive types + +# Anything that can be coerced into numpy.dtype. +# Reference: https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html +DtypeLike = Union[ + dtype, + # default data type (float64) + None, + # array-scalar types and generic types + type, # TODO: enumerate these when we add type hints for numpy scalars + # TODO: add a protocol for anything with a dtype attribute + # character codes, type strings or comma-separated fields, e.g., 'float64' + str, + # (flexible_dtype, itemsize) + Tuple[_DtypeLikeNested, int], + # (fixed_dtype, shape) + Tuple[_DtypeLikeNested, _ShapeLike], + # [(field_name, field_dtype, field_shape), ...] + # + # The type here is quite broad because NumPy accepts quite a wide + # range of inputs inside the list; see the tests for some + # examples. + List[Any], + # {'names': ..., 'formats': ..., 'offsets': ..., 'titles': ..., + # 'itemsize': ...} + # TODO: use TypedDict when/if it's officially supported + Dict[ + str, + Union[ + Sequence[str], # names + Sequence[_DtypeLikeNested], # formats + Sequence[int], # offsets + Sequence[Union[bytes, Text, None]], # titles + int, # itemsize + ], + ], + # {'field1': ..., 'field2': ..., ...} + Dict[str, Tuple[_DtypeLikeNested, int]], + # (base_dtype, new_dtype) + Tuple[_DtypeLikeNested, _DtypeLikeNested], +] + +class _SupportsArray(Protocol): + @overload + def __array__(self, __dtype: DtypeLike = ...) -> ndarray: ... + @overload + def __array__(self, dtype: DtypeLike = ...) -> ndarray: ... + +ArrayLike = Union[bool, int, float, complex, _SupportsArray, Sequence] diff --git a/test_requirements.txt b/test_requirements.txt index 026bbe414bad..b260620217e5 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -7,3 +7,9 @@ pickle5; python_version == '3.7' pickle5; python_version == '3.6' and platform_python_implementation != 'PyPy' # for numpy.random.test.test_extending cffi +# For testing types. Notes on the restrictions: +# - Mypy relies on C API features not present in PyPy +# - Mypy doesn't currently work on Python 3.9 +# - Python 3.6 doesn't work because it doesn't understand py.typed +mypy==0.770; platform_python_implementation != "PyPy" and python_version > "3.6" and python_version < "3.9" +typing_extensions