Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Missing return type hints for Figure#25607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
QuLogic merged 1 commit intomatplotlib:mainfromksunden:figure_types
Apr 29, 2023

Conversation

ksunden
Copy link
Member

PR Summary

Required changes for Figure.figure type hint

The core change here, which enables theplt.figure type hint is that
FigureBase.figure (and subclasses) is now type hinted asFigure instead of
FigureBase.

This is itself predicated on the fact thatFigureBase is never directly instantiated.
Internally, this holds true, and so I'm inclined to think that is the case unless
proven otherwise.
Even our docs forCustom Figure subclasses
subclass fromFigure, notFigureBase.
I will note that FigureBase refers to attributes only defined in the two subclasses:
Figure andSubFigure. (Namely at leastself.patch andself.bbox, may be others)
This to me begs the question of whetherFigureBase should be an Abstract Base Class,
thus defining methods that any valid subclass should have (including those attributes,
which may be required to become properties if so, as I don't think ABC can mark
pure attributes as abstract)

See discussion:#24976 (comment)

  • Decided onFigure instead ofFigureBase
    • FigureBase never instantiated directly (If this assumption is not true, please speak up)
    • SubFigure.figure is alwaysparent.figure
    • Figure.figure is alwaysself
    • Therefore, while SubFigures can be nested arbitrarily,SubFigure.figure is alwaysFigure (GivenFigureBase not instantiated directly)
    • Plus it is what mostusers would expect, I'd think
  • I think the case ofNone was me copying theaxes.Figure was not actually possible
    • Not sure where I thought that was possible, but looking again, not possible

PR Checklist

Documentation and Tests

  • Has pytest style unit tests (andpytest passes)
  • Documentation is sphinx and numpydoc compliant (the docs shouldbuild without error).
  • New plotting related features are documented with examples.

Release Notes

  • New features are marked with a.. versionadded:: directive in the docstring and documented indoc/users/next_whats_new/
  • API changes are marked with a.. versionchanged:: directive in the docstring and documented indoc/api/next_api_changes/
  • Release notes conform with instructions innext_whats_new/README.rst ornext_api_changes/README.rst

@ksundenksunden changed the titleMissing return types for FigureMissing return type hints for FigureApr 3, 2023
@tacaswell
Copy link
Member

diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.pyindex 926944e5a5..c698443e88 100644--- a/lib/matplotlib/figure.py+++ b/lib/matplotlib/figure.py@@ -188,7 +188,6 @@ class FigureBase(Artist):         # axis._get_tick_boxes_siblings         self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()}-        self.figure = self         self._localaxes = []  # track all axes         self.artists = []         self.lines = []@@ -2454,6 +2453,7 @@ None}, default: None             %(Figure:kwdoc)s         """         super().__init__(**kwargs)+        self.figure = self         self._layout_engine = None         if layout is not None:

Should we add this diff to this commit as well? I think I agreeFigureBase should be abstract (but not sure we want to go down concretely making it an abstract base class). I think it is clearer if each of the concrete sub-classes assignself.figure in their init rather than sub-figure re-assigning it. I think this also reduces the slight of hand here?

@ksunden
Copy link
MemberAuthor

There is a slight complexity with that doesn't actually come to light in the current code:

I did figure out that theNone behavior was from copying fromArtist.figure, whichcan be None. So removingself.figure = self from FigureBase actually just inherits that type hint/default of None.

I was worried that that would mean ourif isinstance(num, FigureBase): inplt.figure would then have that type hint, but we are actually saved because the type hint onnum itself is:

num:int|str|Figure|SubFigure|None=None,

So the only valid types that pass that isinstance areFigure andSubFigure, which both have the type of.figure narrowed fromFigureBase | None (the artist type hint) toFigure (which is a valid, narrower, type). Sincenum for that code path must follow both the initial type hint and theisinstance check, the type checker does in fact know thatNone is no longer valid.

It is still a little tricky in ways I don'tfully like, but in ways that can likely be mostly ignored for user code.
It is actually guaranteed that theparent of aSubFigure is aFigure orSubfigure (i.e. not just "anyFigureBase) by the extant type hint and docstring documented expectation.
So the type narrowing byisinstance mixing with the input type is the only real "trick" left.

The full diff (including stubs) is then:

diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.pyindex 926944e5a5..c698443e88 100644--- a/lib/matplotlib/figure.py+++ b/lib/matplotlib/figure.py@@ -188,7 +188,6 @@ class FigureBase(Artist):         # axis._get_tick_boxes_siblings         self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()}-        self.figure = self         self._localaxes = []  # track all axes         self.artists = []         self.lines = []@@ -2454,6 +2453,7 @@ None}, default: None             %(Figure:kwdoc)s         """         super().__init__(**kwargs)+        self.figure = self         self._layout_engine = None          if layout is not None:diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyiindex 2b9f4584e4..15299c2966 100644--- a/lib/matplotlib/figure.pyi+++ b/lib/matplotlib/figure.pyi@@ -70,10 +70,6 @@ class SubplotParams:     ) -> None: ...  class FigureBase(Artist):-    # Small slight-of-hand as FigureBase is not instantiated directly-    # hinting as Figure means e.g. plt.figure can be type hinted more easily-    # FigureBase is never instantiated directly in internal code at the very least-    figure: Figure     artists: list[Artist]     lines: list[Line2D]     patches: list[Patch]@@ -296,6 +292,7 @@ class SubFigure(FigureBase):     def get_axes(self) -> list[Axes]: ...  class Figure(FigureBase):+    figure: Figure     bbox_inches: Bbox     dpi_scale_trans: Affine2D     bbox: Bboxdiff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.pyindex 2933fa5d4c..ace36725e3 100644--- a/lib/matplotlib/pyplot.py+++ b/lib/matplotlib/pyplot.py@@ -858,6 +858,7 @@ default: None     in the matplotlibrc file.     """     if isinstance(num, FigureBase):+        # type narrowed to `Figure | SubFigure` by combination of input and isinstance         if num.canvas.manager is None:             raise ValueError("The passed figure is not managed by pyplot")         _pylab_helpers.Gcf.set_active(num.canvas.manager)

I think I agree, and will add it.

Artist.figure then is the only use ofFigureBase (besides inheritance) in the type hints (pyi stubs or pyplot hints specifically, the isinstance still checks it).

@ksunden
Copy link
MemberAuthor

Cycling to restart CI, as it was failing for unrelated reasons (jupyter) when this was opened. If it is still affected, I'll rebase

@ksundenksunden reopened thisApr 27, 2023
@tacaswell
Copy link
Member

Can we adjust the type onArist.figure as well?

Required changes for Figure.figure type hint
@ksunden
Copy link
MemberAuthor

Rebased to (not quite, dodging#25782) main, updatedArtist.figure type hint toFigure | SubFigure | None

@@ -154,15 +153,15 @@ class FigureBase(Artist):
*,
sharex: bool | Literal["none", "all", "row", "col"] = ...,
sharey: bool | Literal["none", "all", "row", "col"] = ...,
squeeze:Literal[True] = ...,
squeeze:bool = ...,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why did this change?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is a particular case of a potentially more general issue, which was closely related enough to this change that I decided to make the change for this one here.

Essentially while it seems likeLiteral[True] andLiteral[False] should actually spanbool, they technically do not, as they only handle the case of literals being passed, not a variable with typebool.

That said, in many cases it doesn't really makesense to pass anything other than a literal there, so the "passed an unknown bool" case is pretty unexpected.
e.g. thecbook.load_data method has a couple booleans that we would never expect to be passed as an unknown bool (in part because that is a largely for internal use method).

In this particular case a more complete answer would be to try and do our best withoverloads to mete out the case which is passedsqeeze=True withLiteral[1] for nrows/ncols and then have a fallback for this version which can't necessarily differentiate what the return will be.

In fact, unions in returns are generally discouraged and the prevailing wisdom is to just useAny (if overloads cannot properly distinguish the return type). This is actually what I've already done withpyplot.subplots, which calls this method.

Removing such unions is a change I've been leaning towards doing at some point, but made a minimal change here as the return union actually encompasses all possible returns regardless of the value ofsqueeze here.

@QuLogicQuLogic merged commit944155f intomatplotlib:mainApr 29, 2023
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@tacaswelltacaswelltacaswell approved these changes

@QuLogicQuLogicQuLogic approved these changes

Assignees
No one assigned
Projects
None yet
Milestone
v3.8.0
Development

Successfully merging this pull request may close these issues.

4 participants
@ksunden@tacaswell@QuLogic@oscargus

[8]ページ先頭

©2009-2025 Movatter.jp