8000 TYP: regression between 2.1.3 and 2.2.0 (mypy only) · Issue #27957 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

TYP: regression between 2.1.3 and 2.2.0 (mypy only) #27957

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
stefanv opened this issue Dec 9, 2024 · 110 comments · Fixed by #28108
Closed

TYP: regression between 2.1.3 and 2.2.0 (mypy only) #27957

stefanv opened this issue Dec 9, 2024 · 110 comments · Fixed by #28108

Comments

@stefanv
Copy link
Contributor
stefanv commented Dec 9, 2024

Describe the issue:

The following code seems correct, and passes type checking under 2.1.3 but not 2.2.0:

import numpy as np

rng = np.random.default_rng(27446968)
n_states = 5

P = rng.random(size=(n_states, n_states))
p = rng.random(n_states)

p = P.T @ p

I suppose mypy is flagging that the float width can increase, but since float64 is used by random, that won't happen in this scenario.

Reproduce the code example:

N/A

Error message:

foo.py:10: error: Incompatible types in assignment (expression has type "ndarray[tuple[int, ...], dtype[floating[Any]]]", variable has type "ndarray[tuple[int, ...], dtype[float64]]")  [assignment]
Found 1 error in 1 file (checked 1 source file)

Python and NumPy Versions:

Python 3.13, mypy 1.13, numpy 2.2.0

Runtime Environment:

No response

Context for the issue:

No response

@jakevdp
Copy link
Contributor
jakevdp commented Dec 10, 2024

We're seeing these failures in http://github.com/google/jax/ as well.

@jakevdp

This comment was marked as resolved.

@nabenabe0928

This comment was marked as resolved.

@adamjstewart
Copy link
Contributor

Much simpler reproducer:

import numpy as np

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

results in:

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]

If I try:

import numpy as np

x = np.zeros(1, dtype=float)
x = x / 1

I get:

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

Trying:

import numpy as np

x = np.zeros(1, dtype=np.float32)
x = x / 1

gives:

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

Similarly, this:

import numpy as np

x = np.zeros(1, dtype=np.float64)
x = x / 1

gives:

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]

I have no idea how to annotate this in a way that will work.

@jorenham
Copy link
Member
jorenham commented Dec 10, 2024

Looks like this was introduced in #27767: we need tuple[int, ...] rather than tuple[int] in a number of places. cc/ @jorenham

That's unrelated to this issue. Both the left- and the right-hand-side have tuple[int, ...] as shape type.

Since numpy 2.2.0, numpy.float64 is a proper subtype of numpy.floating (and builtins.float). Before, it was simply a type alias, but that was invalid.
What you're seeing is a direct result of that, and it's correct behavior. To illustrate:

from typing import Any
import numpy as np

f: np.floating[Any]
f8: np.float64

f = f8  # accepted
f8 = f  # rejected on >=2.2.0, accepted on <2.2.0

So

The following code seems correct, and passes type checking under 2.1.3 but not 2.2.0:

import numpy as np

rng = np.random.default_rng(27446968)
n_states = 5

P = rng.random(size=(n_states, n_states))
p = rng.random(n_states)

p = P.T @ p

I suppose mypy is flagging that the float width can increase, but since float64 is used by random, that won't happen in this scenario.

Pyright gives a more helpful error message in this case:

Type "floating[Any]" is not assignable to declared type "float64"
  "floating[Any]" is not assignable to "float64"

So the result of rng.random is a float64 array, which causes p to be inferred as a npt.NDArray[np.float64]. But P.T @ p isn't typed as narrowly as it could be (but technically it's valid), so it is inferred as npt.NDArray[np.floating]. And because floating is the supertype of float64, it cannot be assigned to it. And that is correct behavior.

To make this example work without typing errors, this is what you could do

import numpy as np
import numpy.typing as npt

rng = np.random.default_rng(27446968)
n_states = 5

P = rng.random(size=(n_states, n_states))

p: npt.NDArray[np.floating]
p = rng.random(n_states)
p = P.T @ p

@jorenham

This comment was marked as outdated.

@jorenham
Copy link
Member

Much simpler reproducer:

import numpy as np

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

This is yet another issue, and it has to do with shape-typing instead. While technically this isn't a bug, I'll admit that it's very annoying.

The np.zeros(1) returns an array with shape type tuple[int] and scalar type float64, and that actually the best possible way to annotate this.

But currently, the division operator doesn't take shape-typing into account, so x / 1 will ignore the shape-type of the input, and returns the "generic" shape type tuple[int, ...].
But since x already has a more-specific shape-type, it cannot be overwritten with one that is less specific.
It also isn't very precise when it comes to the scalar type, and returns np.floating instead of float64. Technically, that is correct, but I fully agree that it's sub-optimal.

Anyway, this is how you can fix it to work with both mypy and pyright:

import numpy as np
import numpy.typing as npt

x: npt.NDArray[np.floating]
x = np.zeros(1)
x = x / 1

You're other examples, @adamjstewart, can also be fixed in the same way.

Feel free to open a separate issue about these "suboptimally annotated arithmetic operator types".

@jorenham
Copy link
Member

We're seeing these failures in http://github.com/google/jax/ as well.

Could you be more specific?

@jakevdp
Copy link
Contributor
jakevdp commented Dec 10, 2024

We're seeing these failures in http://github.com/google/jax/ as well.

Could you be more specific?

I temporarily pinned NumPy 2.1 yesterday to fix the CI errors, but jax-ml/jax#25381 shows the errors under NumPy 2.2. In particular, this one prompted me to go searching for this issue:

jax/_src/mesh_utils.py:600: error: Incompatible types in assignment (expression has type "ndarray[tuple[int, ...], dtype[Any]]", variable has type "ndarray[tuple[int], dtype[Any]]")  [assignment]

I now see that this is probably the intended behavior: it's no longer safe to assume that an array is an array; every array annotation must now be specialized on its shape (as I said in the other thread, I suspect this will break a lot of valid code).

@jakevdp
Copy link
Contributor
jakevdp commented Dec 10, 2024

To be quite honest, we've been talking about giving up on mypy & static type checking in JAX, because the signal-to-noise ratio of type failures is far too low. This change in NumPy might be the final nail in the coffin for that.

@jorenham
Copy link
Member

valid code

It will by definition only break code that type-checkers (mypy in this case) deems invalid. So in a way, these errors have always been there, but numpy 2.2.0 made it so they now come to light.

As my earlier answer showed, such cases are often easy to work around. And I agree that the mypy error messages are cryptic and difficult to read.

And instead of giving u 8000 p on python typing altogether, perhaps you could switching from mypy to pyright @jakevdp. At the very least, you'd get better error messages, but it also helps a lot if you don't have to deal with all of those mypy bugs anymore (see https://github.com/erictraut/mypy_issues).

@stefanv
Copy link
Contributor Author
stefanv commented Dec 10, 2024

I understand that the change is considered technically more accurate, but from the SP lecture notes' perspective (where I got my example), we'd like to write code that is simple and correct, but without using type annotations. This change makes that quite a bit harder to do.

@jakevdp
Copy link
Contributor
jakevdp commented Dec 10, 2024

And instead of giving up on python typing altogether, perhaps you could switching from mypy to pyright @jakevdp

Is this a sign that the numerical computing community in Python is giving up on mypy as a standard type checker? Your responses here certainly could be read that way. Is that your intent?

@jorenham
Copy link
Member

I understand that the change is considered technically more accurate

That's not necessarily the motivation behind these changes. There has been huge demand for shape-typing support (see e.g. #16544), and these changes are the steps we need to take towards realizing that goal.

@jakevdp
Copy link
Contributor
jakevdp commented Dec 10, 2024

We should work toward implementing features that users are asking for. But if the cost of that is that simple & correct un-annotated code is now incorrectly flagged as a type error, I would contend that it's the implementation that is wrong, not the user code.

@jorenham
Copy link
Member

Is this a sign that the numerical computing community in Python is giving up on mypy as a standard type checker?

There's no hidden agenda or something. It's just that I know first-hand how frustrating it can be to run into mypy bugs time and time again, especially in cases when mypy is the sole reason that I'm not able to implement a good idea. There are also many examples where mypy is preventing bugs from being fixed in typeshed stubs for the python standard library.

So what I'm trying to say is that I'd hate to see people stop typing their code because of their frustrations with mypy. I'm convinced that typing could prevent many bugs, so getting rid of it could have serious consequences for libraries as popular as jax. So that's why I'm trying to show that there are alternatives that could make everyone's life a bit easier 🤷🏻

@jorenham
Copy link
Member

simple & correct un-annotated code

That's not quite right, because when you assign a 1-d array to a 2-d one in your example, you're doing something that's type-unsafe, i.e. something that can lead to bugs. In this case, the only way to prevent this, is using shape-typing.

@jakevdp

This comment was marked as resolved.

@jorenham

This comment was marked as resolved.

@jakevdp

This comment was marked as resolved.

@hauntsaninja
Copy link
Contributor
hauntsaninja commented Dec 27, 2024

Hello! I help maintain mypy and several other projects in the Python typing ecosystem. Static type checking in Python is far from perfect, which is why I've volunteered a lot of my time to these projects.

There's a lot going on in this issue. As far as I can tell, the concrete issues here are:

  • comment 1 jorenham 's numpy PR uses tuple[int] instead of tuple[int, ...]. The first annotation means a tuple containing exactly one int, the second means a tuple of unknown length containing ints. These are two very different types (but yes, this is e.g. a relatively common confusion for folks new to typing).
  • comment 2 Discussion about whether mypy should default to --allow-redefinition (and some closely related behaviour)

mypy sees several orders of magnitude more usage than basedpyright. So it seems less than ideal for users of both mypy and numpy to run into issues.

Maybe folks could post concrete issues in python/mypy#18343 ? Like many open source projects, mypy is entirely driven by volunteer effort, but at least this way folks can track what needs to be done, what configuration needs to be used, or identify cases where the meaning of type annotations is underspecified.

Regarding charris' comment about expertise in typing. If you post at https://github.com/python/typing/discussions or https://discuss.python.org/c/typing/32 you're likely to attract more eyeballs from folks who can bend typing to their will.

Another thing that could be good is type checking downstream projects that depend on numpy in numpy's CI. I set up downstream regression checks for mypy, pyright, typeshed, basedpyright, etc and it's proven quite useful at avoiding regrettable regressions.

Also real quick — I'd also like to contend with KotlinIsland's description of themselves as "closely involved" with mypy. Their primary involvement is maintaining a mypy fork, with very few substantive upstream changes. Projects should of course stand on their merits — I just want to provide some context for the claims to authority in that comment.
(edit: apologies if this paragraph came across as dismissive. It's no easy feat to maintain a fork of mypy; it takes dedication and expertise and I should have been more explicit about that. Hopefully #27957 (comment) better describes what I was trying to clarify)

@jorenham
Copy link
Member
jorenham commented Dec 27, 2024
  • jorenham 's numpy PR uses tuple[int] instead of tuple[int, ...]. The first annotation means a tuple containing exactly one int, the second means a tuple of unknown length containing ints. These are two very different types (but yes, this is e.g. a relatively common confusion for folks new to typing).

In #27767 I intentionally use tuple[int] to annotate a 1-dimensional array. Also, that PR, and shape-typing in general, is not what this issue is about.


  • Discussion about whether mypy should default to --allow-redefinition (and some closely related behaviour)

That's unrelated to this issue, and enabling --allow-redefinition does not result in different outcomes, see #27957 (comment).


mypy sees several orders of magnitude more usage than basedpyright

The results from a recent survey (https://discuss.python.org/t/python-typing-survey-results/71821) with 1677 participants showed that ~60% used mypy, and ~34% used pyright.
To me, that looks like it's the same order of magnitude.


Another thing that could be good is type checking downstream projects that depend on numpy in numpy's CI

See #28054

@jorenham
Copy link
Member

Also real quick — I'd also like to contend with KotlinIsland's description of themselves as "closely involved" with mypy. Their primary involvement is maintaining a mypy fork, with very few substantive upstream changes.

I count 21 merged PR's, 6 open PR's, 151 open issues, and 117 closed issues. Describing that as "very few substantive upstream changes" is not only disrespectful to @KotlinIsland, but to everyone that has invested a similar amount of their time into trying to improve mypy.

It should also be noted that the majority of @KotlinIsland's involvement with the mypy codebase is through the work he's done for basedmypy. A quick glance at the readme should already make that obvious.

@hauntsaninja
Copy link
Contributor
hauntsaninja commented Dec 27, 2024

Wait, what is happening. If anything here is disrespectful to mypy contributors, it's statements like:

as someone that has been closely involved with mypy for many years, my only recommendation is that it should be avoided at all costs. the project is extremely poorly managed and poorly written, and that leads to the issues that everyone that uses it experiences.
ideally it will be deprecated and a new type checker will be pushed forward as the standard. mypy is beyond fixing

My intention with briefly mentioning that KotlinIsland's primary involvement with mypy was their fork and linking to their upstream PRs was to make it clear that statements like ^ aren't the opinion of people who actually maintain upstream mypy.

(You're of course welcome to your opinion — like I said above, projects should stand on their merits. Just worth clarifying the source of that opinion's involvement with upstream, so people don't think it's the equivalent of say, rgommers saying that numpy is "beyond fixing". I wouldn't have said anything if they led with "As someone who forked mypy / As a long time user of mypy / As someone who has contributed several CI improvements to mypy" instead)

Someone shared this link with me, so I came here to see if there was something I could do to help. For worse or for better, mypy has 7x monthly downloads of pyright and 230x basedpyright on PyPI, so it would be good for the community if things just worked. Feel free to post in python/mypy#18343 if there's something I could help with in my volunteer time. Unsubscribing from this issue.

@tyralla
Copy link
tyralla commented Dec 27, 2024

I found this issue when running into the following problem and chatted with hauntsaninja about it:

import numpy
from numpy.typing import NDArray

def f() -> NDArray[numpy.float64]:
    return numpy.zeros(2, dtype=numpy.float64) + numpy.float64(1.0)  # error: Incompatible return value type (got "ndarray[tuple[int, ...], dtype[floating[Any]]]", expected "ndarray[tuple[int, ...], dtype[float64]]")  [return-value]

First, a general remark. Statements like this

So for instance, mypy doesn't support @Property setters, ... But this isn't documented, and they even actively try to make it look like it's supported...

are not only wrong but insulting. Wrong, because Mypy has supported property setters for ages. It only does not support different getter and setter types (I guess this is what jorenham refers to). Insulting (and also wrong), because it suggests that people who contributed to Mypy are trying to mislead the Python community. In the case of different setter types, there was a change in the perception of the desired behaviour. This change is now tricky to implement because Mypy's property support is not consistently programmed with its descriptor support for historical reasons. I do not want to go into more details, but I want to clarify there is no active deception but only limited time. And usually, one (or at least me) finds fixing problems in their free time more attractive than documenting them.

But now to the actual topic. In my (and, I think, also hauntsaninja's) perception, this issue is about incomplete transitions in shape typing (tuple[int] vs tuple[int, ...]) and inheritance (e.g. floating vs float64) in Numpy and that Mypy reports the resulting inconsistencies more frequently than other type checkers, primarily because of --allow-redefinition is not enabled by default. However, jorenham says in his second least comment that neither of these is the topic of this issue. Could you please clarify what defect you mean, then?

The only other hint pointing to another concrete topic I could find is this one:

This is because mypy (unlike pyright) can't understand tuple[int, int, Unpack[tuple[int, ...]], or in words, "a shape type that is at least 1-dimensional".

But, again, I have the feeling you might have encountered a bug but are overgeneralising. Mypy supports Unpack:

from typing import Unpack

x: tuple[int, int, Unpack[tuple[int, ...]]]

x = (1,)  #  error: Incompatible types in assignment (expression has type "tuple[int]", variable has type "tuple[int, int, *tuple[int, ...]]")  [assignment]
x = (1, 2, 3, 4)
reveal_type(x)  # note: Revealed type is "tuple[builtins.int, builtins.int, builtins.int, builtins.int]"

I work with Numpy and Mypy (and sometimes try to improve the latter a little), so I want them to work well together. I hope for a constructive discussion.

@jorenham
Copy link
Member

I found this issue when running into the following problem and chatted with hauntsaninja about it:

import numpy
from numpy.typing import NDArray

def f() -> NDArray[numpy.float64]:
    return numpy.zeros(2, dtype=numpy.float64) + numpy.float64(1.0)  # error: Incompatible return value type (got "ndarray[tuple[int, ...], dtype[floating[Any]]]", expected "ndarray[tuple[int, ...], dtype[float64]]")  [return-value]

I can reproduce this on both pyright and mypy, and this indeed used to be accepted on numpy==2.1.3. So could you open a new issue for this?


Wrong, because Mypy has supported property setters for ages. It only does not support different getter and setter types (I guess this is what jorenham refers to).

When I say that mypy doesn't support something, I mean that within the context of typing, because that's what we're talking about here, and that's what mypy is meant to check.
So for mypy to support something, it should do what is expected of it, and prevent type-unsafe situations like these:

class A:
    @property
    def thing(self) -> str:
        return "thing"
    @thing.setter
    def thing(self, thing: int, /) -> None:
        assert isinstance(thing, int)

a = A()
a.thing = "unsafe"  # mypy allows this

https://mypy-play.net/?mypy=latest&python=3.12&gist=c74a929091e366135306081ea0965098

Mypy explicitly allows this type-unsafe behaviour, but rejects e.g. a.thing = 3.14. And it also allows defining the thing setter with a different type, even though it is known to be unsupported. So this is why the only conclusion I can draw, is that mypy actively hides the fact that is doesn't (fully) support property setters.


because it suggests that people who contributed to Mypy are trying to mislead the Python community

I never mentioned individuals or a group of contributors. I'm purely talking about the software itself. So I don't A3E2 see why you think that I suggesting this.

I thought it would go without saying, but I applaud anyone who contributes to open source, and actively try to motivate people to do so. Mypy is of course no exception to it.
All I'm trying to say is that, there are currently better alternatives to mypy.
And of course doesn't mean that I think that there's some conspiracy going on or something 🤷🏻‍♂️.


this issue is about incomplete transitions in shape typing (tuple[int] vs tuple[int, ...])

No. I've explained this several times now. This issue isn't about shape-typing. And shape-typing isn't yet supported in numpy, so you shouldn't expect it to work.


... and inheritance (e.g. floating vs float64) in Numpy

This issue isn't about inheritance. It's about how a = f(); a = g() was accepted in mypy when f: () -> int and g: () -> int, but isn't anymore now that we've narrowed f to () -> bool, while keeping g the same. This is the same situations as when you'd do a = True; a = 1, which also gets rejected by mypy, and only mypy.


Mypy reports the resulting inconsistencies more frequently than other type checkers, primarily because of --allow-redefinition is not enabled by default

So that's why --allow-redefinition has nothing do do with this.


The only other hint pointing to another concrete topic I could find is this one:

This is because mypy (unlike pyright) can't understand tuple[int, int, Unpack[tuple[int, ...]], or in words, "a shape type that is at least 1-dimensional".

I just checked this again with the latest mypy, and it seems to work now. So it appears my knowledge was outdated, and it's supported now 👌🏻.

@tyralla
Copy link
tyralla commented Dec 27, 2024

I can reproduce this on both pyright and mypy, and this indeed used to be accepted on numpy==2.1.3. So could you open a new issue for this?

Yes, gladly,

So this is why the only conclusion I can draw, is that mypy actively hides the fact that is doesn't (fully) support property setters.

property is very special and requires a lot of special casing. Even Pyright, which, as far as I know, has better support for property, does not try to catch everything. For example, neither Mypy nor Pyright will warn you about this obvious bug:

from __future__ import annotations

def get(self: C) -> int:
    return 1

class C:
    g = property(get)

y = C().g + "x"

There was a general attempt to improve the situation for all type checker maintainers and Python users (by turning property into a descriptor), but that led to nothing (so far) and was even rejected by the Pyright team because of the anticipated work (which I find unfortunate but it's understandable, of course).

As hauntsaninja mentioned, Static type checking in Python is far from perfect and things are complicated, so oversimplified "by the way accusations" are usually not helpful.

But coming back to the main point of this issue (which you seem to have the authority to decide). From the perspective of all numpy users: What would be Mypy's preferred behaviour? I still guess they would want --allow-redefinition enabled by default. I do not know if the limitations of this option are for a good reason or just because the implementation is incomplete (but I could investigate). Also, my preliminary impression is that there should be a way to handle explicitly typed code differently:

x = True
x = 1  # maybe okay

y: bool = True
y = 1  # maybe not okay

What do you think?

@jorenham
Copy link
Member

There was a general attempt to improve the situation for all type checker maintainers and Python users (by turning property into a descriptor), but that led to nothing (so far) and was even rejected by the Pyright team because of the anticipated work (which I find unfortunate but it's understandable, of course).

Yes, I agree that this would be the best way. IMO special casing should only be used in, well, special cases. But @property being nothing but a descriptor, so it's not special (sorry @property; but we still love you regardless).


As hauntsaninja mentioned, Static type checking in Python is far from perfect and things are complicated, so oversimplified "by the way accusations" are usually not helpful.

I completely agree that static type checking is very difficult, especially in a gradual type system like the one Python uses.
That's why it matters so much that we use, and recommend, the best tool to help us with this. And the way to do this, is to lay out the pros and cons of the available tools.

And it's no secret that mypy has a lot more issues than pyright has. See https://github.com/erictraut/mypy_issues for example, for a thorough analysis of this. So what

So the way I see it, is that I'm not saying something new, and not accusing anyone of anything.
I'm just trying to help the numpy developers and users choose the type-checker that's best suited for the job.

Admittedly, I have my frustrations in dealing with mypy for five years or so, and we've seen here that I'm not the only one. But by no means did I intend to insult anyone; my frustrations are with the tool itself, and I having nothing but respect for those that try to make it better.

But coming back to the main point of this issue (which you seem to have the authority to decide). From the perspective of all numpy users: What would be Mypy's preferred behaviour? I still guess they would want --allow-redefinition enabled by default. I do not know if the limitations of this option are for a good reason or just because the implementation is incomplete (but I could investigate). Also, my preliminary impression is that there should be a way to handle explicitly typed code differently:

x = True
x = 1  # maybe okay

y: bool = True
y = 1  # maybe not okay

What do you think?

I agree that this would be the preferred behaviour in this case.

The x type should be determined by looking at how it's used globally. But as far as I can tell, mypy currently decides on the type after looking at x = True, but doesn't consider the following x = 1 after that for this.

The y: bool declaration is explicit, so there's no for the type-checker to infer y. That indeed makes it invalid to y = 1, since 1 isn't assignable to bool, just like isinstance(1, bool) is False.

Coincidentally, pyright already supports this: It accepts x = 1, and only rejects y = 1:

Type "Literal[1]" is not assignable to declared type "bool"
  "Literal[1]" is not assignable to "bool"  (reportAssignmentType)

https://basedpyright.com/?typeCheckingMode=all&code=B4AgvCAqBOCuCmBYAUKCBGEIDEIC2AhgJ4BG8IA9gNbEopEBcIJFFANuFHEskZ5jnzEyIAHYUALpRpEgA

@jorenham
Copy link
Member

But coming back to the main point of this issue (which you seem to have the authority to decide)

That's not really the way things work here. I'm just "the typing guy", and most of what I do in open source is related to static typing in Python. And just like any other NumPy maintainer, I try to do what's best for numpy and its users. In the end, we all decide together.

@KotlinIsland
Copy link
KotlinIsland commented Dec 28, 2024

with allow-redefinition mypy will allow any assignments, as long as the old value has been used at least once:

a: bool = True
a = 1 # error

# but
b: bool = True
b = int(b)  # no error

where as pyright will never allow any redefinitions:

a: bool = True  # error: declaration is obscured
a: int = 1

pyright will allow a variable without an annotation to have assignments that can change the type, as it's not considered:

a = 1
a = ""  # no error

the minuta of the issues that mypy has with this can be demonstrated:

a: str = "a"
print(a)  # use `a` to allow mypy redefinition
a: int
reveal_type(a)  # mypy: int, runtime: "a"

also, mypy doesn't support reassignments across branching logic:

a = True
if bool():
    a = 1  # mypy: error expected bool
else:
    a = "a"  # mypy: error expected bool
reveal_type(a)  # mypy: bool, pyright: Literal[1, "a"]

additionally, I never intended to mislead anyone into thinking I was a mypy collaborator/maintainer. I didn't state as much but I can understand that it could be interpreted that way. I have spent many years developing (based)mypy every day, so I would consider my experience quite a bit more than "has contributed several ci improvements"

additionally-additionally, when I said "mypy is beyond saving", I wasn't trying to personally insult anyone, what I meant was that the issues in mypy are so deep in the implementation that it would require rewriting it to resolve them

I have great respect for open source, Python, mypy and all of its collaborators and contributors. I detest the vitriolic, spiteful and downright nasty not niceness of certain maintainers that exist in this world, so I'm sorry if anything I said was taken personally

@tyralla
Copy link
tyralla commented Dec 29, 2024

Thanks for the clarifying words. I am very happy we left the stumblers behind us and seem to have started a closer collaboration. Numpy and Mypy working well together would be fantastic, and I look forward to Numpy providing shape-typing support for arrays. It's great that you are working on this!

the minuta of the issues that mypy has with this can be demonstrated:

a: bool = True
print(a)  # use `a` to allow mypy redefinition
a: int
reveal_type(a)  # mypy: int, pyright: Literal[True]

I screened through the above comments again and have the impression that users are most bothered that Mypy reports not-so-relevant (from the user perspective) array inconsistencies for unannotated assignments. We internally discussed ways to make Mypy less strict for such cases, even when different scopes are involved. I am (cautiously) optimistic we can make some progress on this soon.

Regarding the given example, I don't see how it relates to this issue. Could you please give an example? (Personally, I like narrowing but don't like and never use allow-redefinition, so I have never really thought it through.)

@stefanv
Copy link
Contributor Author
stefanv commented Dec 29, 2024

To reiterate what @jakevdp said, and what I agree with: "un-annotated user code that executes correctly should not error when type-checked". I care about that case in the context of teaching, and the Scientific Python lecture notes.

@seberg
Copy link
Member
seberg commented Dec 30, 2024

@stefanv was avoiding mypy a viable solution here for you or not? I am not sure if Scientific Python lectures is a typical use-case or not.
But if it isn't a very common use-case and/or pyright (or another work around yet to be discovered!) is feasible than that would be good to know.

EDIT: I suppose this PR answers the question: scipy-lectures/scientific-python-lectures#813 (mostly works, but some other issues remaining).

@jorenham
Copy link
Member

scipy-lectures/scientific-python-lectures#813 (mostly works, but some other issues remaining).

Anything I can help with?

@oscarbenjamin
Copy link

@stefanv was avoiding mypy a viable solution here for you or not? I am not sure if Scientific Python lectures is a typical use-case or not.
But if it isn't a very common use-case and/or pyright (or another work around yet to be discovered!) is feasible than that would be good to know.

Concerning mypy vs pyright I have had similar experience to @jorenham while adding type annotations in SymPy. There are many situations where I can add what seem like the right annotations and everything works with pyright but then I run mypy and it doesn't work and I need to go change the code or the annotations in awkward ways to satisfy mypy.

I think it is worth remembering though that there are different consumers of the type checkers and that these different type checkers are typically used by different people. Downstream of NumPy there are lots of libraries that will for the most part use mypy if they do use a type checker. In this role mypy is a command line development tool that is used somewhat like a linter so the kind of people using it are also the kind of people who might run ruff or flake8 etc on their libraries in CI.

On the other hand there are many "end users" who use NumPy directly but probably do not run any type checker explicitly and certainly would not use mypy. However pyright is a popular editor plugin and is e.g. part of the default Python plugin for vscode. The kind of person who is doing a typical end user calculation with NumPy probably does not add type hints to their code but will often benefit from things like autocompletion provided by pyright based on the type annotations/stubs that are provided by NumPy.

I think that the typical person reading @stefanv's lecture notes does not run mypy. At most they have pyright running in their editor. If thinking about the "end user" experience it is more important to focus on pyright being able to infer the types correctly for users of NumPy's main public API. Importantly you want those users to have no type hints in their own code but for pyright to be able to infer the types of all the numpy objects. Then users can do things like hover their mouse over x and see something like ndarray or type x. and see suggestions for possible array methods and so on. These features are actually very useful in an editor and now that I have pyright running in my editor (vim) I use these a lot and would recommend others to try them out.

For downstream libraries that use type checkers it will be problematic as NumPy adds annotations because it will be a long process during which the annotations are incomplete and continuously changing. Whenever new annotations or stubs are added it will "break" some downstream library's type checking. In some cases that might be a genuine regression and it would be reasonable for NumPy to change/revert the annotations. Otherwise though downstream libraries will just need to adapt and fix their own type annotations. There isn't any way to do the "gradual typing" thing without going through a long process of churn so either NumPy gives up on typing altogether or this downstream library typing churn has to be accepted.

There will be many downstream libraries who are using mypy in combination with the NumPy stubs regardless of whether NumPy "blesses" any other type checker. If, like in the OP issue, someone has problems that are specific to mypy then I think it is perfectly reasonable for NumPy to point out that the problem is specific to mypy and that mypy has configuration options and also that they can use a different checker if they want.

The problematic behaviour that mypy is showing here is just that mypy disallows rebinding the type like this:

data = '123'
data = int(data) # mypy errors but fine with pyright

Personally I don't want to use a checker that behaves like that and I much prefer pyright's handling of this case.

If someone else is happy with this style of type checking then they can feel free to use mypy but as NumPy (and other libraries) add more annotations it is going to break their mypy CI jobs. This is unavoidable since fewer things will be inferred as Any and mypy will inevitably consider more assignments to be incompatible. There is a simple fix to make mypy happy:

data = '123'
data_int = int(data)

Or they can configure mypy or they can use a different checker.

If NumPy wants to use a type checker on its own internal code then it can choose whichever one it wants. If pyright seems best then just use pyright. It doesn't matter what anyone else thinks or what checker they use. I haven't used basedpyright but having a lot of experience of mypy and pyright I would recommend pyright over mypy.

@jorenham jorenham removed the 57 - Close? Issues which may be closable unless discussion continued label Jan 6, 2025
robbiemu added a commit to robbiemu/llama.cpp that referenced this issue May 16, 2025
…ying class (such as np.float32)

looking at microsoft/pyright#9051, they declined to fix it themselves, and suggested instead that the used must add a # pyright: ignore or # type: ignore directive to suppress this error.

Numpy is working to resolve them: numpy/numpy#28076 and has already done so with npfloat64 (which I can verify in our errors) -- see numpy/numpy#27957 .
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.

0