Repository: matplotlib/matplotlib
Pull request: Add explicit overloads for pyplot.{show,subplot}
<!--
Thank you so much for your PR!  To help us review your contribution, please check
out the development guide https://matplotlib.org/devdocs/devel/index.html
-->

## PR summary
This PR update `lib/matplotlib/pyplot.py` to add overloads for the following methods:
- `subplot`
- `show`


## PR checklist
- [x] Closes #30160 
- [x] Changed code is [tested](https://matplotlib.org/devdocs/devel/testing.html)

Problem statement:
[MNT]: pyplot type hints
### Summary

Is there any reason why some functions in `pyplot.py` use `*args` and `**kwargs` as their only parameters (e.g. show, figlegend, or savefig) and therefore have untyped parameters?

### Proposed fix

Specify the actual signature or type parameters with Unpack[TypedDict] and Unpack[tuple] (however, parameter names are lost here)

Hints from issue discussion:
It depends: in the majority of cases e.g. `savefig`, `figlegend` these are customized wrappers and `*args, **kwargs` are only shorthands to pass though all parameters. You could spell them out explicitly at the cost of having to ensure consistency manually whenever the signature changes. There's a part of pyplot wrappers that we auto-generate using `boilerplate.py`. Their signatures are automatically spelled out. You could add a similar mechanism for the above cases.

`show()` is different in that the supported parameters depend on the backend. Since third party backends can define additional parameters, its signature is truly dynamic.
If you are worried about typing, presumably you are writing scripts and/or library code not working in an interactive shell.  If that is case, than you may be better off using the explicit Axes API (https://matplotlib.org/stable/users/explain/figure/api_interfaces.html#the-explicit-axes-interface) instead of the implicit pyplot API.
I looked at `boilerplate.py` and I think I can improve it to adding any methods (like `xlim` or `ylim`)
Regarding typing methods based on the backend, why not use `@overload` to define the possible signature (as indicated in the documentation or in the `_Backend` class) ?

For example with show:
```py
@overload
def show(*, block: bool | None = None) -> None:...

@overload
def show(*args: Any, **kwargs: Any) -> None:...

def show(*args, **kwargs) -> None:
    # Function here
```
`xlim` and `ylim` are actually not good examples at they have a a relative complex signature. the *args, **kwargs.


Checking the whole of pyplot, there's actually not too much we can gain from automatic rewriting. Instead, I think we can get the signatures cheaper be writing out the few relevant cases explicitly and adding a test that the signatures are still in sync. Something like
```
def test_signatures_identical():
    # for manually written forwarding functions in pyplot, check that
    # the signatures are in sync
    assert (inspect.signature(plt.set_loglevel).parameters
            == inspect.signature(mpl.set_loglevel).parameters)
```

The list of `*args, **kwargs` functions is

| function |  comment |
| -- | -- |
| set_loglevel | can be written out explicitly / copied + test |
| new_figure_manager | special (dynamically overwritten at runtime) - leave as is |
| draw_if_interactive | special (dynamically overwritten at runtime) - leave as is |
| show | adding the overload is ok |
| savefig | turn into `savefig(fname : [...], **kwargs)` |
| figlegend | more complex as the wrapped `Figure.legend` is also `*args, **kwargs`. Maybe simply add overloads? |
| subplot | add overloads |
| xlim / ylim | could be written out explicitly / copied + test |
| polar | could be written out explicitly / copied + test |



I have improved `boilerplate.py` to automatically generate xlim and ylim on `pyplot.py`. I think we can use the same system to generate `xscale` / `yscale` and `xlabel` / `ylabel` (this will generate a single method to get or set it, like the current `xlim` / `ylim`). I'm not sure about title because the getter takes a parameter.
Furthermore, I don't know how to create their tests because there are overloads.

Is there any reason why we don't put the `transparent` keyword argument ?
As for the others, I will manually copy the signature and create the tests to check it.
See https://github.com/matplotlib/matplotlib/issues/24413 re: savefig()'s signature.

Commit message: Create subplot overloads on pyplot.py

(cherry picked from commit f4693e3c984f4775e29c3d10e48f58ae7de84e98)

Patch to review:
diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py
index cf5c9b4b739f..6a72bc091b0c 100644
--- a/lib/matplotlib/pyplot.py
+++ b/lib/matplotlib/pyplot.py
@@ -569,6 +569,14 @@ def draw_if_interactive(*args, **kwargs):
     return _get_backend_mod().draw_if_interactive(*args, **kwargs)
 
 
+@overload
+def show() -> None: ...
+
+
+@overload
+def show(block: bool) -> None: ...
+
+
 # This function's signature is rewritten upon backend-load by switch_backend.
 def show(*args, **kwargs) -> None:
     """
@@ -1393,6 +1401,22 @@ def cla() -> None:
 
 ## More ways of creating Axes ##
 
+@overload
+def subplot(nrows: int, ncols: int, index: int, /, **kwargs): ...
+
+
+@overload
+def subplot(pos: int | SubplotSpec, /, **kwargs): ...
+
+
+@overload
+def subplot(ax: Axes, /): ...
+
+
+@overload
+def subplot(**kwargs): ...
+
+
 @_docstring.interpd
 def subplot(*args, **kwargs) -> Axes:
     """


Code review comments:
diff_hunk: @@ -569,6 +569,14 @@ def draw_if_interactive(*args, **kwargs):
     return _get_backend_mod().draw_if_interactive(*args, **kwargs)
 
 
+@overload
+def show() -> None: ...
+
+
+@overload
+def show(block: bool) -> None: ...
path: lib/matplotlib/pyplot.py
text: @user1:
`pyplot_show` (which is what is used to overwrite the signature by `switch_backend`) has [`block` as keyword-only and optional](https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/backend_bases.pyi#L426). Why can't this also be a single overload of the same form?

At least, `block` should be keyword-only as well, shouldn't it?

@author:
You're right, I also forgot the `**kwargs` for specific backends.
I need to write the two because Python require 2 overloads at least in addition to the implementation. Now, I copy the signature because `show(*, block, **kwargs)` and `show(**kwargs)` are considered as the same or broader by mypy.

diff_hunk: @@ -1393,6 +1401,22 @@ def cla() -> None:
 
 ## More ways of creating Axes ##
 
+@overload
+def subplot(nrows: int, ncols: int, index: int, /, **kwargs): ...
+
+
+@overload
+def subplot(pos: int | SubplotSpec, /, **kwargs): ...
+
+
+@overload
+def subplot(ax: Axes, /): ...
path: lib/matplotlib/pyplot.py
text: @user1:
I don't see this form documented as acceptable, nor does it work.

@user2:
This was inferred from the docstring
```
call signatures:
    ...
    subplot(ax)
```

But indeed this does not work. It was intoduced in the docstring in #11459 and I suppose it was a false generalization because it also added `plt.add_axes(ax)`, which is valid. This and the corresponding docstring line should be removed.

@author:
I have removed the overload and this point on the docstring. Not sure, but I think I have seen another methods with the same docstring error.