Description
Summary
The data
keyword argument presents a tricky edge case for typing that is currently not ideal.
#25632 Provides the first step, untyped but present in the type stubs (so stubtest
will check them at least.
Proposed fix
The OO api type hints: overloads
The most properly the type hint would look something like:
@overload
def hlines(
self,
y: float | ArrayLike,
xmin: float | ArrayLike,
xmax: float | ArrayLike,
colors: Sequence[ColorType] | None = ...,
linestyles: LineStyleType = ...,
label: str = ...,
*,
data: None =...,
**kwargs
) -> LineCollection: ...
@overload
def hlines(
self,
y: float | ArrayLike | str,
xmin: float | ArrayLike | str,
xmax: float | ArrayLike | str,
colors: Sequence[ColorType] | None | str = ...,
linestyles: LineStyleType = ...,
label: str = ...,
*,
data: Mapping[str, ArrayLike],
**kwargs
) -> LineCollection: ...
That is to say:
An overloaded call where one call has data
of type None
, and the other has data
with type Mapping[str, ArrayLike]
(and also appends str
to the valid types for affected parameters)
Note that technically str
does pass as ArrayLike
, as that is a broad union which includes scalars (and str
specifically), but communicating explicitly that str
is expected for the case where data is not None is a bit better.
If **kwargs
is not included, the first overload could simply omit data
all together, making it a required keyword arg.
The problem: pyplot
Current pyplot generation code (tools/boilerplate.py) only looks at the first signature of overloads.
This currently only affects a small number of methods that are in the autogenerated portion of pyplot and have overloads in their definitions (e.g. Axes.legend), which simply don't get the full type hints in pyplot.
Currently, none of the affected methods result in a type hint being added which is not correct, just type hints being omitted (and thus to mypy are Any
) and so not deriving the value of having type hints for that small number of methods
The above overload for data
would not be the same, as it would either get None
OR Mapping[...]
, not the union. In the latter case, this would be a problem as the default would not match the hint. In the former, it would fail if you tried to use the arg. If no **kwargs
and data is simply omitted, it would simply be as it already was where pyplot gets no type hint for data
, but it does appear in the signature.
The pyplot generation code may need to be be more comprehensive and explicitly handle the case of overloads. This is certainly possible, but not easy.
Considerations for inline type hints
Since data
does not appear in the actual signature this is mildly complicated for if we ever do want to move towards inline type hints. Such a move is not in the current plans, but worth writing down why this is a complicating factor.
If we use overloads, I think it may actually just wash away, so maybe not as complicated as otherwise.
PEP 612 addresses allowing static typing of decorators which modify signatures by adding positional arguments, but explicitly excludes the case of adding a keyword-only arg, as is the case for the _preprocess_data
. So statically using the decorator to transparently modify the signature for the type checker is not possible.