8000 TYP: Suboptimally annotated arithmetic operator types · Issue #27965 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

TYP: Suboptimally annotated arithmetic operator types #27965

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

Closed
adamjstewart opened this issue Dec 10, 2024 · 1 comment · Fixed by #28108
Closed

TYP: Suboptimally annotated arithmetic operator types #27965

adamjstewart opened this issue Dec 10, 2024 · 1 comment · Fixed by #28108

Comments

@adamjstewart
Copy link
Contributor

Describe the issue:

Arithmetic operators like division do not take shape typing into account, resulting in false-positive type hinting issues.

Originally reported here: #27957 (comment)
Some debugging work done by @jorenham at here: #27957 (comment)
Opening a separate bug report as requested

Reproduce the code example:

import numpy as np

x = np.zeros(1)
x = x / 1

Error message:

test.py:4: error: Incompatible types in assignment (expression has type "ndarray[tuple[int, ...], dtype[floating[Any]]]", variable has type "ndarray[tuple[int], dtype[float64]]")  [assignment]

Python and NumPy Versions:

2.2.0
3.13.0 (main, Nov 21 2024, 10:46:46) [Clang 16.0.0 (clang-1600.0.26.4)]

Type-checker version and settings:

mypy 1.11.2 (compiled: no)
mypy --strict

Additional typing packages.

No response

@jorenham
Copy link
Member

this example shows why type-checkers reject the x = x / 1 from your example"

# issue_27965.pyi

import numpy as np

reveal_type(x := np.zeros(1))
reveal_type(x / 1)

relevant mypy output:

[...]/issue_27965.pyi:3: note: Revealed type is "numpy.ndarray[tuple[builtins.int], numpy.dtype[numpy.float64]]"
[...]/issue_27965.pyi:4: note: Revealed type is "numpy.ndarray[builtins.tuple[builtins.int, ...], numpy.dtype[numpy.floating[Any]]]"

relevant pyright output:

[...]/issue_27965.pyi:3:13 - information: Type of "x := np.zeros(1)" is "ndarray[tuple[int], dtype[float64]]"
[...]/issue_27965.pyi:4:13 - information: Type of "x / 1" is "ndarray[tuple[int, ...], dtype[floating[Any]]]"

The np.zeros(1) is inferred by both mypy and pyright as a ndarray with 1-d shape and a dtype of float64. This is correct in the most accurate way that's (reasonably) possible.

But np.zeros(1) / 1 isn't as precisely typed, and is inferred as ndarray with ?-d shape and a dtype of floating.

Now the problem emerges when we do x = x / 1, which means we're assigning ndarray[tuple[int, ...], floating] to ndarray[(int), float64]. But this is invalid, because

  1. tuple[int, ...] isn't assignable to tuple[int]
  2. floating isn't assignable to float64 ()

This is because we can't assign something less specific to something more specific, just like you can't assign int to a bool.

But if we flip the LHS and RHS, then it actually is allowed. So in the same way that bool is assignable to int, tuple[int] is assignable to tuple[int, ...] and float64 is assignable to floating.

We can use this to create a very clean workaround to this issue:

import numpy as np
import numpy.typing as npt

x: npt.NDArray[np.floating]  # ndarray[tuple[int, ...], dtype[floating]]
x = np.zeros(1)  # accepted
x = x / 1  # accepted

Note that this workaround is only required for mypy users: Pyright infers the type of x as ndarray[tuple[int, ...], dtype[floating]], because it looks at more than one line of code at a time, and isn't "greedy" (algorithmically speaking) like mypy is.

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

Successfully merging a pull request may close this issue.

2 participants
0