Skip to content

Issues related to numpy #18343

Description

@hauntsaninja

This can be a tracking issue.

See also numpy/numpy#27957

Activity

  1. added
    bugmypy got something wrong
    metaIssues tracking a broad area of work
    and removed
    bugmypy got something wrong
    on Dec 26, 2024
  2. hauntsaninja commented on Dec 28, 2024

    @hauntsaninja
    CollaboratorAuthor

    PR adding mypy_primer to numpy's CI: numpy/numpy#28073

  3. hauntsaninja commented on Dec 28, 2024

    @hauntsaninja
    CollaboratorAuthor

    PR fixing a crash when using numpy with weird import following settings: #18351

  4. hauntsaninja commented on Dec 28, 2024

    @hauntsaninja
    CollaboratorAuthor

    PR improving diagnostics for some case someone ran into using numpy: #18352

  5. jorenham commented on Dec 28, 2024

    @jorenham
    Contributor

    Not sure if this has been reported before, but the following line is causing stubtest to crash with numpy>=2.2.0 in case you have an empty numpy array as argument default (which is what least scipy==1.14.1 does):

    if runtime_arg.default != inspect.Parameter.empty:

    Because since NumPy 2.2.0, ndarray.__bool__ will raise if the array is empty. And the reason why __bool__ gets called, is because ndarray.__ne__ returns another array with np.bool dtype of the same shape:

    >>> import numpy as np
    >>> np.__version__
    '2.2.0'
    >>> a = np.array([])
    >>> a
    array([], dtype=float64)
    >>> a == object()  # numpy "vectorizes" the `==` over each element
    array([], dtype=bool)
    >>> bool(a == object())
    Traceback (most recent call last):
      File "<python-input-24>", line 1, in <module>
        bool(a == object())
        ~~~~^^^^^^^^^^^^^^^
    ValueError: The truth value of an empty array is ambiguous. Use `array.size > 0` to check that an array is not empty.

    So when stubtest encounters a def f(a=np.array([])): ... (on the .py side), stubtest will crash:

    $ stubtest --mypy-config-file=pyproject.toml --allowlist=.mypyignore --ignore-unused-allowlist scipy
    Traceback (most recent call last):
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/bin/stubtest", line [8](https://github.com/jorenham/scipy-stubs/actions/runs/12246812991/job/34163454128?pr=288#step:7:9), in <module>
        sys.exit(main())
                 ~~~~^^
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.13/site-packages/mypy/stubtest.py", line 20[9](https://github.com/jorenham/scipy-stubs/actions/runs/12246812991/job/34163454128?pr=288#step:7:10)8, in main
        return test_stubs(parse_options(sys.argv[1:]))
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.13/site-packages/mypy/stubtest.py", line 1971, in test_stubs
        for error in test_module(module):
                     ~~~~~~~~~~~^^^^^^^^
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.13/site-packages/mypy/stubtest.py", line 247, in test_module
        yield from verify(stub, runtime, [module_name])
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.13/site-packages/mypy/stubtest.py", line 423, in verify_mypyfile
        yield from verify(stub_entry, runtime_entry, object_path + [entry])
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.13/site-packages/mypy/stubtest.py", line 578, in verify_typeinfo
        yield from verify(stub_to_verify, runtime_attr, object_path + [entry])
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.13/site-packages/mypy/stubtest.py", line [10](https://github.com/jorenham/scipy-stubs/actions/runs/12246812991/job/34163454128?pr=288#step:7:11)64, in verify_funcitem
        for message in _verify_signature(stub_sig, runtime_sig, function_name=stub.name):
                       ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.[13](https://github.com/jorenham/scipy-stubs/actions/runs/12246812991/job/34163454128?pr=288#step:7:14)/site-packages/mypy/stubtest.py", line 902, in _verify_signature
        yield from _verify_arg_default_value(stub_arg, runtime_arg)
      File "/home/runner/work/scipy-stubs/scipy-stubs/.venv/lib/python3.13/site-packages/mypy/stubtest.py", line 664, in _verify_arg_default_value
        if runtime_arg.default != inspect.Parameter.empty:
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ValueError: The truth value of an empty array is ambiguous. Use `array.size > 0` to check that an array is not empty.
    Error: Process completed with exit code 1.

    Luckily, fixing this is just a matter of replacing != with is not when comparing against inspect.Parameter.empty (which happens in at least 2 two other places in mypy/stubtest.py as well, btw).

    I could scrape together a PR if you'd like, but that could take some time (of which I don't have a lot the coming week) as I don't have a local dev env for mypy setup at the moment. And I can imagine that's more mypy-savvy could probably fix this in a coffee break on their nokia 3310 (assuming they don't get distracted and end up playing snake 2, of course). Anyway, I wouldn't mind either way, so let me know 🤷🏻.

  6. hamdanal commented on Dec 28, 2024

    @hamdanal
    Collaborator

    Not sure if this has been reported before, but the following line is causing stubtest to crash with numpy>=2.2.0 in case you have an empty numpy array as argument default (which is what least scipy==1.14.1 does):

    Thanks for the report, fix is here #18353

  7. jorenham commented on Dec 29, 2024

    @jorenham
  8. jorenham commented on Jan 5, 2025

    @jorenham
    Contributor

    It's not possible to annotate the following behavior:

    >>> class Spam: ...
    >>> np.object_(Spam())
    <__main__.Spam object at 0x7f536d7b5160>

    And it leads to incorrectly inferred types

    class Spam: ...
    reveal_type(np.object_(Spam()))   # numpy.object_  (wrong; should be `Spam`)

    Pyright correctly reports Type of "np.object_(Spam())" is "Spam".


    The other numpy "scalar" constructors either return an instance of themselves, or an ndarray, dependening on the input. For example:

    >>> np.int8(42)
    np.int8(42)
    >>> np.int8([42])
    array([42], dtype=int8)

    The bare minimum stub looks like

    class int8:
        @overload
        def __new__(cls, x: int = 0, /) -> Self: ...
        @overload
        def __new__(cls, x: Sequence[int], /) -> np.ndarray[tuple[int], np.dtype[np.int8]]: ...

    But a false positive (according to the typing spec) is reported:

    Incompatible return type for "__new__" (returns "ndarray[tuple[int], dtype[signedinteger[_8Bit]]]", but must return a subtype of "int8")
    

    But even so, the second overload isn't completely ignored, because the following isn't reported as an error:

    reveal_type(int8(42))  # int8  (correct)
    reveal_type(int8([42]))  # int8  (wrong; should be `numpy.ndarray`)

    So it's as if the return type of second __new__ overload is ignored, and Self is used instead 🤔


    I believe that #15182 is at least in part responsible for this behavior.


    Oh and you might already know this, but this is also blocking a typeshed fix for (at least) builtins.reversed annotations (python/typeshed#11646). So the amount of people that will be happy once this is resolved isn't limited to the numpy community :)

  9. jorenham commented on Jan 21, 2025

    @jorenham
    Contributor
  10. matteobrv commented on Jan 29, 2025

    @matteobrv
  11. 23 remaining items

  12. jorenham commented on Apr 1, 2026

    @jorenham
    Contributor

    @jorenham: We now in fact consider including an (optional) NumPy plugin in Mypy. Personally, I am longing for fewer unexpected Any return values and better shape typing (possibly including simple math). See this issue for a (very rapidly prototyped) prototype.

    I know you prefer some degree of synchronicity among type checkers. But are you nevertheless interested in sharing your opinion on which plugin features could help a significant number of NumPy and Mypy users?

    I noticed that you use Literal integers in the shape type in your example. However, as I explained in https://discuss.python.org/t/pep-827-type-manipulation/106353/88 and numpy/numtype#578, I would recommend against doing that.

    The cumsum example in the referenced issue is something that could (and should) be fixed in the numpy stubs. It should be too difficult, since it's a shape-preserving operation. So if you truly wish to help a significant number of NumPy users, then I'd say contributing stubs improvements to this class of functions would be the way to go.

    However, there some cases where it's currently impossible to statically determine the output shape type, but where a mypy plugin would be able to. An rather extreme example of this is numpy.einsum, where the output shape is determined through a string using a particular mini-syntax.

    Then there's also a third category of functions where even a mypy plugin wouldn't be able to determine the shape-type, for example numpy.load.

    So to summarize; a mypy plugin for shape-typing would be helpful for functions whose output shape-type can statically be determined yet cannot be expressed using Python's type system. I'm not sure which ones exactly these are, but there are probably only a handful of them.

  13. tyralla commented on Apr 1, 2026

    @tyralla
    Collaborator

    Thanks for your insights, @jorenham!

    I noticed that you use Literal integers in the shape type in your example. However, as I explained in https://discuss.python.org/t/pep-827-type-manipulation/106353/88 and numpy/numtype#578, I would recommend against doing that.

    I know you thought about this much more thoroughly, but I am still optimistic that shape typing (using Literal) would be very helpful and may not be impossible (though maybe too much effort).

    As I see it, the main points in your cited comments are...

    Upcasting problems (Literal[1] to int)

    Opposed to basedpyright, Mypy keeps the Literal type in the given example:

    from typing import Literal
    
    def unwrap[N: int](shape: tuple[N]) -> N:
        return shape[0]
    
    s: tuple[Literal[1]] = (1,)
    reveal_type(unwrap(s))  # note: Revealed type is "Literal[1]"

    Missing integer arithmetic logic in type checkers

    That would be one task of the pugin. We could teach it that np.reshape(np.ndarray[tuple[Literal[12]], Any], (4, -1)) -> np.ndarray[tuple[Literal[4], Literal[3]], Any]. (Admittedly, lots of work.)

    False positives (or, more correctly, unclear positives)

    If a function only works for ndarray[tuple[Literal[2], Literal[2]], Any], I would be happy if Mypy would complain if I insert ndarray[tuple[int, int], Any]

    The cumsum example in the referenced issue is something that could (and should) be fixed in the numpy stubs. It should be too difficult, since it's a shape-preserving operation. So if you truly wish to help a significant number of NumPy users, then I'd say contributing stubs improvements to this class of functions would be the way to go.

    I picked this example absolutely randomly. But yes, making the stubs perfect where possible is preferable, of course. For me, it is hard to predict which parts of the NumPy stubs can be (or will be) improved substantially.

    Thanks for your recommendations again. I'll let them sink in for a while.

  14. ilevkivskyi commented on Apr 6, 2026

    @ilevkivskyi
    Member

    As someone who used to be an active NumPy user for ~10 years (a little known fact about me: I have PhD in theoretical physics) here are my 2 cents:

    • There are two different levels of "precision" when typing arrays: just the number of dimensions, and actual array shape. In my experience it would be great to have the second. There were countless times when after waiting 5 minutes I discovered that I have an off-by-one error because of np.diff(), or some matrices won't multiply because I forgot .T.
    • I don't really buy the argument that (exact) Literal types for array shapes will cause a lot of harm because we don't have a "gradual" equivalent of int. First, if plugin would only (or mostly) infer precise types for arrays (not for functions that accept them), this will not be a problem unless an array appears in invariant position, which is rarely the case. Second, in the worst case we can always add mypy_extensions.AnyInt.
  15. jorenham commented on Apr 6, 2026

    @jorenham
    Contributor
    • I don't really buy the argument that (exact) Literal types for array shapes will cause a lot of harm because we don't have a "gradual" equivalent of int. First, if plugin would only (or mostly) infer precise types for arrays (not for functions that accept them), this will not be a problem unless an array appears in invariant position, which is rarely the case. Second, in the worst case we can always add mypy_extensions.AnyInt.

    If such an AnyInt would exist in Python, and if there'd be some way to statically infer that e.g. np.empty(12).reshape((3,-1)) has shape (3, 4), that np.swapaxes([[1, 2, 3], [4, 5, 6]], -1, -2) has shape (3, 2), etc., then I'd be all for it.

    But don't get me wrong; AFAIK it perfectly fine if someone creates a mypy plugin to help with shape-typing in NumPy. It's just not something that we're going to build ourselves or require our users to use; that's all.

  16. ilevkivskyi commented on May 21, 2026

    @ilevkivskyi
    Member

    @jorenham @MarcoGorelli Could you please add more links to issues affecting Python numeric stack here?

  17. jorenham commented on May 21, 2026

    @jorenham
    Contributor

    There's #11347 amd #14070 that can lead to unexpected np.ndarray inferred dtypes in certain situations. We often use the "Never trick" to work around this (because pyright also has a similar issue), but there are still some places where it can still happen.

    There are also quite a lot of # type: ignore[override] in the numpy stubs that I believe to be false positives. And although I haven't investigated this in detail, I wouldn't be surprised if at least some of those are related to #12379 and #20557 .

    I also ran into #9464 a couple of times. But Eric Traut once told me that fixing this could be very complex, because it would require solving constraint sets using something like a unification algorithm. But if mypy already has the machinery for that in place, perhaps it's not as difficult as it would be for pyright 🤷

  18. jorenham commented on Aug 9, 2026

    @jorenham
  19. jorenham commented on Oct 7, 2026

    @jorenham
    Contributor

    I ran into #14764 a couple of times today when auditing the NumPy stubs for bugs. For example:

    import numpy as np
    import numpy.typing as npt
    
    def f(x: npt.NDArray[np.float64], b: bool) -> None:
        np.histogram(x, bins=20, density=b)

    reports

    No overload variant of "histogram" matches argument types "ndarray[tuple[Any, ...], dtype[float64]]", "int", "bool"
    

    and np.histogram has exhaustive pairs of density: Literal[True] and density: Literal[False] overloads: https://github.com/numpy/numpy/blob/f5cac4c74a4e0793df8defa73c824a75f60a8c7d/numpy/lib/_histograms_impl.pyi#L91-L399

    I guess I could work around this by adding a bunch of overloads with density: bool for each True/False pair, but seeing as histogram already has 35 overloads now, I'm sure you understand why I'd rather not :P.

    Maybe you're already aware of this (in which case I apologize for mansplaining :P), but the overload spec argument type expansion section states that

    bool should be expanded into Literal[True] and Literal[False]

    which doesn't seem to be happening here.

  20. ilevkivskyi commented on Oct 7, 2026

    @ilevkivskyi
    Member

    @jorenham Yeah, that issue was already on my radar, there is already a WIP implementation btw. Hopefully a fix for it will be in v2.5.

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

    metaIssues tracking a broad area of work

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions