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.
Summary
The
datakeyword 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
stubtestwill check them at least.Proposed fix
The OO api type hints: overloads
The most properly the type hint would look something like:
That is to say:
An overloaded call where one call has
dataof typeNone, and the other hasdatawith typeMapping[str, ArrayLike](and also appendsstrto the valid types for affected parameters)Note that technically
strdoes pass asArrayLike, as that is a broad union which includes scalars (andstrspecifically), but communicating explicitly thatstris expected for the case where data is not None is a bit better.If
**kwargsis not included, the first overload could simply omitdataall 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 methodsThe above overload for
datawould not be the same, as it would either getNoneORMapping[...], 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**kwargsand data is simply omitted, it would simply be as it already was where pyplot gets no type hint fordata, 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
datadoes 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.