Repository: matplotlib/matplotlib
Pull request: added typing_extensions.Self to _AxesBase.twinx
## PR summary
- adds `typing_extensions >= 4.0.0` as a dependency
- annotates `_AxesBase.twinx` and `_AxesBase.twiny` with return type `typing_extensions.Self`
- [x] closes #28624 
- [N/A] new and changed code is [tested](https://matplotlib.org/devdocs/devel/testing.html)
- [N/A] *Plotting related* features are demonstrated in an [example](https://matplotlib.org/devdocs/devel/document.html#write-examples-and-tutorials)
- [N/A] *New Features* and *API Changes* are noted with a [directive and release note](https://matplotlib.org/devdocs/devel/api_changes.html#announce-changes-deprecations-and-new-features)
- [N/A] Documentation complies with [general](https://matplotlib.org/devdocs/devel/document.html#write-rest-pages) and [docstring](https://matplotlib.org/devdocs/devel/document.html#write-docstrings) guidelines

<!--We understand that PRs can sometimes be overwhelming, especially as the
reviews start coming in.  Please let us know if the reviews are unclear or
the recommended next step seems overly demanding, if you would like help in
addressing a reviewer's comments, or if you have been waiting too long to hear
back on your PR.-->


Problem statement:
[Bug]: Bad type hint in `_AxesBase.twinx()`
### Bug summary

Incorrect type annotation for `_AxesBase.twinx()` (should be `Self`, not `_AxesBase`).

### Code for reproduction

```Python
import matplotlib.pyplot as plt
import numpy as np

# First create some toy data:
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x**2)
z = np.cos(x**2)

# Create four polar Axes and access them through the returned array
fig, axsA = plt.subplots()
axsA.plot(x, y)
axsB = axsA.twinx()
axsB.plot(x, z)  # <-- Cannot access attribute "plot" for class "_AxesBase"
```

### Actual outcome

axsB is of type `_AxesBase`

### Expected outcome

The type of `axsB` should be the same as the type of `axsA`.

### Matplotlib Version

3.9.1

Hints from issue discussion:
Potentially fixing this requires adding `typing_extensions` as a dependency, since `typing.Self` is not available in 3.10.
Otherwise, users might not have it in their virtual environment when installing `matplotlib`, leaving the stubs broken.

Commit message: added typing_extensions.Self to _AxesBase.twinx

Patch to review:
diff --git a/environment.yml b/environment.yml
index 264f02800690..3ca4ca6b5050 100644
--- a/environment.yml
+++ b/environment.yml
@@ -28,6 +28,7 @@ dependencies:
   - python-dateutil>=2.1
   - setuptools_scm
   - wxpython
+  - typing-extensions>=4.0.0
   # building documentation
   - colorspacious
   - graphviz
diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi
index 1fdc0750f0bc..4903bb41645e 100644
--- a/lib/matplotlib/axes/_base.pyi
+++ b/lib/matplotlib/axes/_base.pyi
@@ -27,6 +27,7 @@ from cycler import Cycler
 import numpy as np
 from numpy.typing import ArrayLike
 from typing import Any, Literal, TypeVar, overload
+from typing_extensions import Self
 from matplotlib.typing import ColorType
 
 _T = TypeVar("_T", bound=Artist)
@@ -384,8 +385,8 @@ class _AxesBase(martist.Artist):
         bbox_extra_artists: Sequence[Artist] | None = ...,
         for_layout_only: bool = ...
     ) -> Bbox | None: ...
-    def twinx(self) -> _AxesBase: ...
-    def twiny(self) -> _AxesBase: ...
+    def twinx(self) -> Self: ...
+    def twiny(self) -> Self: ...
     def get_shared_x_axes(self) -> cbook.GrouperView: ...
     def get_shared_y_axes(self) -> cbook.GrouperView: ...
     def label_outer(self, remove_inner_ticks: bool = ...) -> None: ...
diff --git a/pyproject.toml b/pyproject.toml
index 0f181ccb629e..b7663c968878 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,6 +39,7 @@ dependencies = [
     "pillow >= 8",
     "pyparsing >= 2.3.1",
     "python-dateutil >= 2.7",
+    "typing-extensions >= 4.0.0",
 ]
 requires-python = ">=3.10"
 


Code review comments:
diff_hunk: @@ -39,6 +39,7 @@ dependencies = [
     "pillow >= 8",
     "pyparsing >= 2.3.1",
     "python-dateutil >= 2.7",
+    "typing-extensions >= 4.0.0",
path: pyproject.toml
text: @user1:
Also not needed.

diff_hunk: @@ -28,6 +28,7 @@ dependencies:
   - python-dateutil>=2.1
   - setuptools_scm
   - wxpython
+  - typing-extensions>=4.0.0
path: environment.yml
text: @user1:
We already use `typing-extensions` for `Self`; this addition is not needed as a runtime requirement. It is pulled in by mypy.

@author:
There are other type checkers than `mypy`, for instance `pyright` does not have any python dependencies as it is written in TS. Type-checkers may also run in external virtual environments, such as pre-commit hooks.

IMO if someone created a bare venv and only pip installed matplotlib, then stuff should still type check.

@author:
I will double-check if it is needed tomorrow.


@user1:
This file is not for venvs, but conda; we don't have any typing-related dependencies here. They're handled by pre-commit or the tox environment.

@author:
OK, so I tested it: downloaded current release from PyPI and installed in a bare virtualenv together with pyright (python wrapper)

```
$ pip list
Package         Version
--------------- -----------
contourpy       1.2.1
cycler          0.12.1
fonttools       4.53.1
kiwisolver      1.4.5
matplotlib      3.9.1
nodeenv         1.9.1
numpy           2.0.1
packaging       24.1
pillow          10.4.0
pip             24.2
pyparsing       3.1.2
pyright         1.1.373
python-dateutil 2.9.0.post0
setuptools      68.2.2
six             1.16.0
wheel           0.41.2
```

Seems like it works after doing the minimal edit, despite `typing-extensions` not being present in the virtual environment.