"Invalid self argument" for numpy's `iadd`
To Reproduce
from typing import Any
import numpy as np
import numpy.typing as npt
def plus1(array: npt.NDArray[np.integer[Any]]) -> None:
array += 1
Expected Behavior
No error
Actual Behavior
error: Invalid self argument "ndarray[tuple[int, ...], dtype[integer[Any]]]" to attribute function "iadd" with type "Callable[[ndarray[tuple[int, ...], dtype[numpy.bool[builtins.bool]]], _SupportsArray[dtype[numpy.bool[builtins.bool]]] | _NestedSequence[_SupportsArray[dtype[numpy.bool[builtins.bool]]]] | builtins.bool | _NestedSequence[builtins.bool]], ndarray[_ShapeT_co, _DType_co]]" [misc]
Your Environment
- Mypy version used: 1.16.0
- Mypy command-line flags: none
- Mypy configuration options from
mypy.ini(and other config files): none - Python version used: 3.13.2
- Numpy version used: 2.2.6
@bersbersbers Is it wrong to use union as follows?
import numpy as np
import numpy.typing as npt
from typing import Any
def plus1(array: npt.NDArray[np.uint8 | np.int8]) -> npt.NDArray[np.uint8 | np.int8]:
return array + 1
It wouldn't be wrong necessarily, if I enumerated all possible integer types. Question is, why should I need to do that, considering that np.integer exists? :)
I also tried this, with varying success:
from typing import Any
import numpy as np
import numpy.typing as npt
# works
def plus1a(array: npt.NDArray[np.signedinteger[Any]]) -> None:
array += 1
# works
def plus1b(array: npt.NDArray[np.unsignedinteger[Any]]) -> None:
array += 1
# fails
def plus1c(array: npt.NDArray[np.signedinteger[Any] | np.unsignedinteger[Any]]) -> None:
array += 1
# fails
def plus1d(array: npt.NDArray[np.integer[Any]]) -> None:
array += 1
It's defined as follows in the numpy stubs:
@overload
def __iadd__(self: NDArray[np.bool], other: _ArrayLikeBool_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(
self: NDArray[unsignedinteger[Any]],
other: _ArrayLikeUInt_co | _IntLike_co,
/,
) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[signedinteger[Any]], other: _ArrayLikeInt_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[float64], other: _ArrayLikeFloat_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[floating[Any]], other: _ArrayLikeFloat_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[complex128], other: _ArrayLikeComplex_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[complexfloating[Any]], other: _ArrayLikeComplex_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[timedelta64], other: _ArrayLikeTD64_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[datetime64], other: _ArrayLikeTD64_co, /) -> ndarray[_ShapeT_co, _DType_co]: ...
@overload
def __iadd__(self: NDArray[object_], other: Any, /) -> ndarray[_ShapeT_co, _DType_co]: ...
https://github.com/numpy/numpy/blob/2b686f659642080e2fc708719385de6e8be0955f/numpy/init.pyi#L3334-L3357
So since there is no self: integer, self: number, or self: generic, mypy rejects this call to __iadd__. Mypy 1.15.0 and 1.16.0 behave in the same way, so this is not a mypy regression or something.
It's worth noting that pyright does not reject this. I'm guessing that it falls back to __add__ if the overloads of __iadd__ are exhausted.
But even so, I agree that your example should be accepted by all type-checkers. I'll try to get this fixed before the upcoming numpy 2.3 release, but it might not make it in time (2.3 is about to be released).
edit: see https://github.com/numpy/numpy/pull/29092
@jorenham thanks for taking care of this. Just one bit:
Mypy 1.15.0 and 1.16.0 behave in the same way
I disagree at least partially - at least in my code base, this issue started appearing after 1.16.0 was released, and going back to 1.15.0 fixes it. Maybe my MWE above also triggers something in 1.15, but something definitely changed with 1.16.
I'll try to get this fixed before the upcoming numpy 2.3 release, but it might not make it in time (2.3 is about to be released).
This will be fixed in the upcoming numpy 2.3.0 release.
Is this fixed now? I run into a similar problem with numpy version 2.3.3. The only difference is that I use np.floating and np.number, which mypy still seems to complain about for __iadd__ and __isub__. The linked PR does not seem to address these cases.
Is this fixed now? I run into a similar problem with
numpyversion 2.3.3. The only difference is that I usenp.floatingandnp.number, whichmypystill seems to complain about for__iadd__and__isub__. The linked PR does not seem to address these cases.
Ah I didn't realize that this was also affecting number and floating, since that wasn't mentioned in the OP. I'll try to get those ones fixed as well then.
I guess the whole numpy type hierarchy is in principle affected. It would be great if there was a more systematic way to include these type hints (instead of spelling out each alternative individually).
It would be great if there was a more systematic way to include these type hints (instead of spelling out each alternative individually).
There is :) I've implemented it in https://github.com/numpy/numtype (not in numpy itself because that'd be backwards incompatible)