Repository: matplotlib/matplotlib
Pull request: Add explicit getter / setter overloads for pyplot.{xlim,ylim}
<!--
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 the `tools/boilerplate.py` for autogenerate getter / setter:
- `Axes.xlim`
- `Axes.ylim`
- Any method with the following form: `T.get_meth() -> TGet` ; `T.set_meth(...) -> TSet`


## 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: Revert boilerplate.py and manually copy xlim and ylim overloads on pyplot.py

Patch to review:
diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py
index cf5c9b4b739f..c16b79895759 100644
--- a/lib/matplotlib/pyplot.py
+++ b/lib/matplotlib/pyplot.py
@@ -2096,6 +2096,25 @@ def box(on: bool | None = None) -> None:
 ## Axis ##
 
 
+@overload
+def xlim() -> tuple[float, float]:
+    ...
+
+
+# Autogenerated by boilerplate.py.  Do not edit as changes will be lost.
+@overload
+def xlim(
+        left: float | tuple[float, float] | None = None,
+        right: float | None = None,
+        *,
+        emit: bool = True,
+        auto: bool | None = False,
+        xmin: float | None = None,
+        xmax: float | None = None,
+) -> tuple[float, float]:
+    ...
+
+
 def xlim(*args, **kwargs) -> tuple[float, float]:
     """
     Get or set the x limits of the current Axes.
@@ -2133,6 +2152,24 @@ def xlim(*args, **kwargs) -> tuple[float, float]:
     return ret
 
 
+@overload
+def ylim() -> tuple[float, float]:
+    ...
+
+
+@overload
+def ylim(
+        bottom: float | tuple[float, float] | None = None,
+        top: float | None = None,
+        *,
+        emit: bool = True,
+        auto: bool | None = False,
+        ymin: float | None = None,
+        ymax: float | None = None,
+) -> tuple[float, float]:
+    ...
+
+
 def ylim(*args, **kwargs) -> tuple[float, float]:
     """
     Get or set the y-limits of the current Axes.


Code review comments:
diff_hunk: @@ -2096,6 +2096,25 @@ def box(on: bool | None = None) -> None:
 ## Axis ##
 
 
+@overload
+def xlim() -> tuple[float, float]:
+    ...
+
+
+# Autogenerated by boilerplate.py.  Do not edit as changes will be lost.
path: lib/matplotlib/pyplot.py
text: @user1:
This isn't autogenerated.

@author:
Oops, forgot that one — fixed!