3

I have a function that looks as following:

import numpy as np

def test() -> None:
    a = map(np.radians, (1.,2.,np.array([1,2,3])))

Evaluating this with mypy returns the error message

error: Argument 1 to "map" has incompatible type "ufunc"; expected "Callable[[object], Any]"

Using only floats or only arrays as the input list for map poses no problem here, the issue arises when the input list/tuple contains objects of both types. At runtime this poses no problem either. How can I modify this function to satisfy mypy's requirements and make it typesafe?

dmmpie
  • 365
  • 2
  • 14
  • 2
    Like `def test() -> None: f = np.radians # type: Any; a = map(f, (1., 2., np.array([1, 2, 3])))`? You just want to get rid of the error message? – Thomas Weller Dec 13 '21 at 16:55
  • @ThomasWeller is there a better way to do this? Is this a bug that is just inherent in numpy or is it something that can be properly done on my side? – dmmpie Dec 13 '21 at 21:53
  • Personally, I'm using Pycharm. It makes use of type hints as well and also gives warnings, but it's not as strict as mypy. Why do you use mypy? To write better code or is it enforced by your employer? – Thomas Weller Dec 13 '21 at 22:31
  • @ThomasWeller we have a software that we would like to make typesafe and are making use of mypy for that purpose. How would you recommend we handle this problem? Would it make sense to add a new line for this or to simply not annotate the enitire function? – dmmpie Dec 14 '21 at 00:06
  • 1
    Sorry, I'm not an expert on this. Let's wait what other people say. I think the following: a) you know that it will not have runtime errors b) it seems to be more a numpy issue how it declares methods than your issue. E.g. a `def test() -> None: f = lambda x: np.radians(x); a = map(f, (1., 2., np.array([1, 2, 3])))` will also not have a mypy report either, but you're probably going to lose the performance (vectorization). Sp personally, I'd probably go with `# type: ignore` for the whole line. – Thomas Weller Dec 14 '21 at 06:26
  • Perhaps related: https://stackoverflow.com/questions/49220022/how-can-mypy-ignore-a-single-line-in-a-source-file – Thomas Weller Dec 14 '21 at 06:27

3 Answers3

2

The problem here is that mypy infers the type of the elements of (1., 2., np.array([1,2,3])) as object (not as Union[float, np.ndarray] as you'd hope here), which then is incompatible with np.radians.

What you can do as a workaround is giving your tuple/list an explicit type that is compatible with both the tuple/list and the argument of np.radians. E.g.:

from typing import Sequence, Union
import numpy as np

def test() -> None:
    x: Sequence[Union[float, np.ndarray]] = (1., 2., np.array([1,2,3]))
    a = map(np.radians, x)

There is an open mypy github issue that seems similar, though maybe not exactly the same: python/mypy#6697.

mihi
  • 3,097
  • 16
  • 26
0

With all due respect, the right solution is to tell MyPy to ignore this line.

import numpy as np

def test() -> None:
    a = map(np.radians, (1.,2.,np.array([1.,2.,3.]))) # type: ignore

PS: You always have the very inadvisable option to edit the numpy library and fix the call signature of _UFunc_Nin1_Nout1 to accept the object type (in addition to ArrayLike). The edit below makes your mypy error message go away.

--- a/typing/_ufunc.pyi
+++ b/typing/_ufunc.pyi
@@ -86,21 +86,21 @@ class _UFunc_Nin1_Nout1(ufunc, Generic[_NameType, _NTypes, _IDType]):
         casting: _Casting = ...,
         order: _OrderKACF = ...,
         dtype: DTypeLike = ...,
         subok: bool = ...,
         signature: Union[str, _2Tuple[Optional[str]]] = ...,
         extobj: List[Any] = ...,
     ) -> Any: ...
     @overload
     def __call__(
         self,
-        __x1: ArrayLike,
+        __x1: ArrayLike | object,
         out: Union[None, NDArray[Any], Tuple[NDArray[Any]]] = ...,
         *,
         where: Optional[_ArrayLikeBool_co] = ...,
         casting: _Casting = ...,
         order: _OrderKACF = ...,
         dtype: DTypeLike = ...,
         subok: bool = ...,
         signature: Union[str, _2Tuple[Optional[str]]] = ...,
         extobj: List[Any] = ...,
     ) -> NDArray[Any] | Any: ...

References

user23952
  • 578
  • 3
  • 10
0

I don't know if it was intentional, but you didn't use dots in the array.

import numpy as np

def test() -> None:
    a = map(np.radians, (1.,2.,np.array([1.,2.,3.])))