-
Notifications
You must be signed in to change notification settings - Fork 41
Tips to make Mypy happy
Here are some of the most common MyPy problems we often run into, the examples might avoid a long search on the web. A summary of points to quickly get to the details:
- How to define overloading of a function when mypy complains about different possible outputs;
- How to order overloading definitions to avoid "overloading signature X will never be matched";
- How to deal with overloading dependent on a Literal (e.g.
tag=True
) for their output; - The special case of overloading based on a parameter located after a default argument;
In detail:
- You have defined a function, but the output depends on the input?
def curvature(
dem: np.ndarray | np.ma.masked_array | RasterType,
resolution: float | tuple[float, float] | None = None,
use_richdem: bool = False,
) -> np.ndarray | Raster:
You can specify this with @overloading
directly before the function, as follows:
@overload
def curvature(
dem: RasterType,
resolution: float | tuple[float, float] | None,
use_richdem: bool = False,
) -> Raster: ...
@overload
def curvature(
dem: np.ndarray | np.ma.masked_array,
resolution: float | tuple[float, float] | None,
use_richdem: bool = False,
) -> np.ndarray: ...
And don't forget: Always repeat all default parameters when making the overload, or you'll have some nasty errors!
-
Is the above really going to work? Well, actually, no! Order matters! Mypy generally considers custom-defined types such as
RasterType
asAny
. So the first@overload
can takeAny
as input, which includes anything... including the NumPy arrays defined in the second@overload
. In order to work, one always needs to write in last the overload with the broader input type, and the first one with the narrower one! -
The output depends on the value of a parameter? Consider using
Literal
, for example:
def get_nanarray(self, return_mask: bool = False) -> np.ndarray | tuple[np.ndarray, np.ndarray]:
Can be constrained by adding this (Note: for some reason, the default needs to be re-specified in its Literal occurence):
@overload
def get_nanarray(self, return_mask: Literal[False] = False) -> np.ndarray:
...
@overload
def get_nanarray(self, return_mask: Literal[True]) -> tuple[np.ndarray, np.ndarray]:
...
- When the parameter is located after a default argument, use a
*
to help MyPy look in the right place, and repeat the default function state in one last overload:
@overload
def crop(
self: RasterType,
cropGeom: RasterType | Vector | list[float] | tuple[float, ...],
mode: Literal["match_pixel"] | Literal["match_extent"] = "match_pixel",
*,
inplace: Literal[True] = True,
) -> None:
...
@overload
def crop(
self: RasterType,
cropGeom: RasterType | Vector | list[float] | tuple[float, ...],
mode: Literal["match_pixel"] | Literal["match_extent"] = "match_pixel",
*,
inplace: Literal[False],
) -> RasterType:
...
@overload
def crop(
self: RasterType,
cropGeom: RasterType | Vector | list[float] | tuple[float, ...],
mode: Literal["match_pixel"] | Literal["match_extent"] = "match_pixel",
inplace: bool = True,
) -> RasterType | None:
...