numpy/numpy

TYP: numpy allows a datetime array to be divided by another array

Closed this issue · 6 comments

Dr-Irv commented

Describe the issue:

If a numpy array has a datetime type, and is divided by another array, typecheckers are not picking that up as wrong.

Reproduce the code example:

from typing import reveal_type
import numpy as np

s = np.array([np.datetime64(f"2025-10-{d:02d}") for d in (23, 24, 25)], np.datetime64)
t = np.array([1, 2, 3])
reveal_type(s)
reveal_type(t)
reveal_type(s.__truediv__(t))

s / t

Error message:

The type revealed of the division operation is `Any`

Python and NumPy Versions:

python 3.12
numpy 2.3.4

Type-checker version and settings:

pyright 1.1.407
mypy 1.18.2

Additional typing packages.

Note: This came up with pandas-stubs.

We think the solution is to add an overload that returns Never .

This only seems to happen when dividing by an array with dtype[Any], in which case the latest overload is selected:

numpy/numpy/__init__.pyi

Lines 3154 to 3184 in a0ce2fb

# Keep in sync with `MaskedArray.__truediv__`
@overload
def __truediv__(self: _ArrayInt_co | NDArray[float64], other: _ArrayLikeFloat64_co, /) -> NDArray[float64]: ...
@overload
def __truediv__(self: _ArrayFloat64_co, other: _ArrayLikeInt_co | _ArrayLike[floating[_64Bit]], /) -> NDArray[float64]: ...
@overload
def __truediv__(self: NDArray[complex128], other: _ArrayLikeComplex128_co, /) -> NDArray[complex128]: ...
@overload
def __truediv__(self: _ArrayComplex128_co, other: _ArrayLike[complexfloating[_64Bit]], /) -> NDArray[complex128]: ...
@overload
def __truediv__(self: NDArray[floating], other: _ArrayLikeFloat_co, /) -> NDArray[floating]: ...
@overload
def __truediv__(self: _ArrayFloat_co, other: _ArrayLike[floating], /) -> NDArray[floating]: ...
@overload
def __truediv__(self: NDArray[complexfloating], other: _ArrayLikeNumber_co, /) -> NDArray[complexfloating]: ...
@overload
def __truediv__(self: _ArrayNumber_co, other: _ArrayLike[complexfloating], /) -> NDArray[complexfloating]: ...
@overload
def __truediv__(self: NDArray[inexact], other: _ArrayLikeNumber_co, /) -> NDArray[inexact]: ...
@overload
def __truediv__(self: NDArray[number], other: _ArrayLikeNumber_co, /) -> NDArray[number]: ...
@overload
def __truediv__(self: NDArray[timedelta64], other: _ArrayLike[timedelta64], /) -> NDArray[float64]: ...
@overload
def __truediv__(self: NDArray[timedelta64], other: _ArrayLikeBool_co, /) -> NoReturn: ...
@overload
def __truediv__(self: NDArray[timedelta64], other: _ArrayLikeFloat_co, /) -> NDArray[timedelta64]: ...
@overload
def __truediv__(self: NDArray[object_], other: Any, /) -> Any: ...
@overload
def __truediv__(self: NDArray[Any], other: _ArrayLikeObject_co, /) -> Any: ...

So this false negaative could be fixed by narrowing the self: NDArray[Any] so that it doesn't accept NDArray[np.datetime64] anymore.

Dr-Irv commented

This only seems to happen when dividing by an array with dtype[Any], in which case the latest overload is selected:

It also happens with anything that supports the __array__() interface, which is also true for pandas. So if the divisor is a pandas series, we can't detect that.

This only seems to happen when dividing by an array with dtype[Any], in which case the latest overload is selected:

It also happens with anything that supports the __array__() interface, which is also true for pandas. So if the divisor is a pandas series, we can't detect that.

Hmm, that sounds more like the second-to-last __rtruediv__ overload.

I take it that pd.Series.__array__ returns NDArray[Any] then?

Dr-Irv commented

I take it that pd.Series.__array__ returns NDArray[Any] then?

Yes

Following the conversation in #30174, I'll be closing this. Not because it's "as designed", but because it's not possible to express this in a valid way and without it causing problems for other problems. We can re-open once that changes.