Skip to content

Commit

Permalink
MNT: Deprecate changing Figure.number
Browse files Browse the repository at this point in the history
Historically, pyplot dynamically added a number attribute to figure.
However, this number must stay in sync with the figure manger.
AFAICS overwriting the number attribute does not have the desired
effect for pyplot. But there are some repos in GitHub that do change
number. So let's take it slow and properly migrate away from writing.

Making the dynamic attribute private and wrapping it in a property
allows to maintain current behavior and deprecate write-access.

When the deprecation expires, there's no need for duplicate state
anymore and the private _number attribute can be replaced by
`self.canvas.manager.num` if that exists and None otherwise.

Also closes matplotlib#28994.
  • Loading branch information
timhoffm committed Oct 23, 2024
1 parent ac1d009 commit fd55b30
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 1 deletion.
6 changes: 6 additions & 0 deletions doc/api/next_api_changes/deprecations/28007-TH.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Changing ``Figure.number``
~~~~~~~~~~~~~~~~~~~~~~~~~~

Changing ``Figure.number`` is deprecated. This value is used by `.pyplot`
to identify figures. It must stay in sync with the pyplot internal state
and is not intended to be modified by the user.
2 changes: 1 addition & 1 deletion lib/matplotlib/_pylab_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def _set_new_active_manager(cls, manager):
manager._cidgcf = manager.canvas.mpl_connect(
"button_press_event", lambda event: cls.set_active(manager))
fig = manager.canvas.figure
fig.number = manager.num
fig._number = manager.num
label = fig.get_label()
if label:
manager.set_window_title(label)
Expand Down
27 changes: 27 additions & 0 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2808,6 +2808,33 @@ def axes(self):

get_axes = axes.fget

@property
def number(self):
"""The figure id, used to identify figures in `.pyplot`."""
# Historically, pyplot dynamically added a number attribute to figure.
# However, this number must stay in sync with the figure manager.
# AFAICS overwriting the number attribute does not have the desired
# effect for pyplot. But there are some repos in GitHub that do change
# number. So let's take it slow and properly migrate away from writing.
#
# Making the dynamic attribute private and wrapping it in a property
# allows to maintain current behavior and deprecate write-access.
#
# When the deprecation expires, there's no need for duplicate state
# anymore and the private _number attribute can be replaced by
# `self.canvas.manager.num` if that exists and None otherwise.
if hasattr(self, '_number'):
return self._number
else:
raise AttributeError(
"'Figure' object has no attribute 'number'. In the future this"
"will change to returning 'None' instead.")

@number.setter
def number(self, num):
_api.warn_deprecated("3.10", message="Changing 'Figure.number' is deprecated")
self._number = num

def _get_renderer(self):
if hasattr(self.canvas, 'get_renderer'):
return self.canvas.get_renderer()
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/figure.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ class Figure(FigureBase):
def get_layout_engine(self) -> LayoutEngine | None: ...
def _repr_html_(self) -> str | None: ...
def show(self, warn: bool = ...) -> None: ...
@property
def number(self) -> int | str: ...
@number.setter
def number(self, num: int | str) -> None: ...
@property # type: ignore[misc]
def axes(self) -> list[Axes]: ... # type: ignore[override]
def get_axes(self) -> list[Axes]: ...
Expand Down
20 changes: 20 additions & 0 deletions lib/matplotlib/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ def test_figure_label():
with pytest.raises(ValueError):
plt.figure(Figure())

def test_figure_label_replaced():
plt.close('all')
fig = plt.figure(1)
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match="Changing 'Figure.number' is deprecated"):
fig.number = 2
assert fig.number == 2

def test_figure_no_label():
# standalone figures do not have a figure attribute
fig = matplotlib.figure.Figure()
with pytest.raises(AttributeError):
fig.number
# but one can set one
fig.number = 5
with pytest.warns(MatplotlibDeprecationWarning,
match="Changing 'Figure.number' is deprecated"):
assert fig.number == 5
# even though it's not known by pyplot
assert not plt.fignum_exists(fig.number)

def test_fignum_exists():
# pyplot figure creation, selection and closing with fignum_exists
Expand Down

0 comments on commit fd55b30

Please sign in to comment.