Thanks to visit codestin.com
Credit goes to github.com

Skip to content

TYP: Return type for logical_or too narrow #28162

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
MarDiehl opened this issue Jan 16, 2025 · 4 comments · Fixed by #28168
Closed

TYP: Return type for logical_or too narrow #28162

MarDiehl opened this issue Jan 16, 2025 · 4 comments · Fixed by #28168
Assignees

Comments

@MarDiehl
Copy link

Describe the issue:

It seems that logical_or is annotated to return "ndarray[tuple[int, ...], dtype[Any]]", but it returns a single bool when called with scalar arguments. Probably other logical_xxx functions show the same behavior.

I could try to fix it (Returning a union of bool and the current annotation), but need someone pointing me at the location of the annotation.

Reproduce the code example:

import numpy as np

components_proper = np.random.random([3,3]) - .5
components_improper = np.random.random([3,3]) - .5

if np.random.randint(1,3) > 1:
    in_SST_ = np.logical_or(np.all(components_proper   >= 0.0,axis=-1),
                            np.all(components_improper >= 0.0,axis=-1))
else:
    in_SST_ = np.all(components_proper >= 0.0,axis=-1)

Error message:

error: Incompatible types in assignment (expression has type "numpy.bool[builtins.bool] | ndarray[tuple[int, ...], dtype[numpy.bool[builtins.bool]]]", variable has type "ndarray[tuple[int, ...], dtype[Any]]")  [assignment]
Found 1 error in 1 file (checked 1 source file)

Python and NumPy Versions:

NumPy 2.2.1
Python 3.13.1

Type-checker version and settings:

MyPy: 1.14.0

Additional typing packages.

n/a

@jorenham
Copy link
Member

This error is caused by a limitation in mypy's type inference system, and won't occur with other typecheckers like pyright or basedpyright. See #27957 for a (rather lengthy) discussion on this in detail.

Luckily, there's an easy workaround:

import numpy as np
import numpy.typing as npt

components_proper = np.random.random([3, 3]) - 0.5
components_improper = np.random.random([3, 3]) - 0.5

in_SST_: npt.NDArray[np.bool] | np.bool
if np.random.randint(1, 3) > 1:
    in_SST_ = np.logical_or(
        np.all(components_proper >= 0.0, axis=-1),
        np.all(components_improper >= 0.0, axis=-1),
    )
else:
    in_SST_ = np.all(components_proper >= 0.0, axis=-1)

This helps mypy to understand what the common denominator of in_SST_ is, because it isn't able to figure that it by itself.
If possible for you, you might also want to consider switching to pyright or basedpyright, which don't have this issue.

But even so, you're right in saying that the return type of logical_or is too narrow. Because, like np.all, its return type should also include to possibility for scalars, not only arrays.
But as you can see in

@overload
def __call__(
self,
__x1: _ScalarLike_co,
__x2: _ScalarLike_co,
out: None = ...,
*,
where: None | _ArrayLikeBool_co = ...,
casting: _CastingKind = ...,
order: _OrderKACF = ...,
dtype: DTypeLike = ...,
subok: bool = ...,
signature: str | _3Tuple[None | str] = ...,
) -> Any: ...
@overload
def __call__(
self,
__x1: ArrayLike,
__x2: ArrayLike,
out: None | NDArray[Any] | tuple[NDArray[Any]] = ...,
*,
where: None | _ArrayLikeBool_co = ...,
casting: _CastingKind = ...,
order: _OrderKACF = ...,
dtype: DTypeLike = ...,
subok: bool = ...,
signature: str | _3Tuple[None | str] = ...,
) -> NDArray[Any]: ...
, the second that accepts any "array-like" (which includes scalars), is annotated to return NDArray[Any]. If a union of scalar and array is passed, we get an incorrect result.

Somehow this has gone by unnoticed for 4 years, so thanks for reporting this!

Some related issues:

@jorenham
Copy link
Member

FYI, there's a newer v1.14.1 mypy release available

@jorenham jorenham self-assigned this Jan 16, 2025
@MarDiehl
Copy link
Author

@jorenham
thanks for the quick reply and the detailed explanation.

Actually, I think everything is correct for the typehint in line 190, at least for the case I had in mind. If only one of the arguments of np.logical_and is a scalar, the return value is an array. Only two scalars (the other @overload) will give a scalar. The problem is just that mypy apparently infers the type from the first assignment instead of checking all assignments for a common denominator.

@jorenham
Copy link
Member

The bug is rather subtle:

import numpy as np
import numpy.typing as npt

a: npt.NDArray[np.float64] | np.float64
b: npt.NDArray[np.float64] | np.float64
out = np.logical_or(a, b)
reveal_type(out)  # npt.NDArray[Any]

So out is inferred as array, even though both inputs could also be a scalar.

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