Skip to content

"Invalid self argument" for numpy's iadd #19171

Open
numpy/numpy
#29092
@bersbersbers

Description

@bersbersbers

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

Activity

iwakitakuma33

iwakitakuma33 commented on May 30, 2025

@iwakitakuma33

@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
bersbersbers

bersbersbers commented on May 30, 2025

@bersbersbers
Author

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
jorenham

jorenham commented on May 30, 2025

@jorenham
Contributor

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 numpy/numpy#29092

bersbersbers

bersbersbers commented on May 30, 2025

@bersbersbers
Author

@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.

jorenham

jorenham commented on Jun 2, 2025

@jorenham
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @jorenham@bersbersbers@iwakitakuma33@sterliakov

      Issue actions

        "Invalid self argument" for numpy's `iadd` · Issue #19171 · python/mypy