Skip to content

Commit fd55b30

Browse files
committed
MNT: Deprecate changing Figure.number
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.
1 parent ac1d009 commit fd55b30

File tree

5 files changed

+58
-1
lines changed

5 files changed

+58
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Changing ``Figure.number``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Changing ``Figure.number`` is deprecated. This value is used by `.pyplot`
5+
to identify figures. It must stay in sync with the pyplot internal state
6+
and is not intended to be modified by the user.

lib/matplotlib/_pylab_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def _set_new_active_manager(cls, manager):
108108
manager._cidgcf = manager.canvas.mpl_connect(
109109
"button_press_event", lambda event: cls.set_active(manager))
110110
fig = manager.canvas.figure
111-
fig.number = manager.num
111+
fig._number = manager.num
112112
label = fig.get_label()
113113
if label:
114114
manager.set_window_title(label)

lib/matplotlib/figure.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2808,6 +2808,33 @@ def axes(self):
28082808

28092809
get_axes = axes.fget
28102810

2811+
@property
2812+
def number(self):
2813+
"""The figure id, used to identify figures in `.pyplot`."""
2814+
# Historically, pyplot dynamically added a number attribute to figure.
2815+
# However, this number must stay in sync with the figure manager.
2816+
# AFAICS overwriting the number attribute does not have the desired
2817+
# effect for pyplot. But there are some repos in GitHub that do change
2818+
# number. So let's take it slow and properly migrate away from writing.
2819+
#
2820+
# Making the dynamic attribute private and wrapping it in a property
2821+
# allows to maintain current behavior and deprecate write-access.
2822+
#
2823+
# When the deprecation expires, there's no need for duplicate state
2824+
# anymore and the private _number attribute can be replaced by
2825+
# `self.canvas.manager.num` if that exists and None otherwise.
2826+
if hasattr(self, '_number'):
2827+
return self._number
2828+
else:
2829+
raise AttributeError(
2830+
"'Figure' object has no attribute 'number'. In the future this"
2831+
"will change to returning 'None' instead.")
2832+
2833+
@number.setter
2834+
def number(self, num):
2835+
_api.warn_deprecated("3.10", message="Changing 'Figure.number' is deprecated")
2836+
self._number = num
2837+
28112838
def _get_renderer(self):
28122839
if hasattr(self.canvas, 'get_renderer'):
28132840
return self.canvas.get_renderer()

lib/matplotlib/figure.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ class Figure(FigureBase):
343343
def get_layout_engine(self) -> LayoutEngine | None: ...
344344
def _repr_html_(self) -> str | None: ...
345345
def show(self, warn: bool = ...) -> None: ...
346+
@property
347+
def number(self) -> int | str: ...
348+
@number.setter
349+
def number(self, num: int | str) -> None: ...
346350
@property # type: ignore[misc]
347351
def axes(self) -> list[Axes]: ... # type: ignore[override]
348352
def get_axes(self) -> list[Axes]: ...

lib/matplotlib/tests/test_figure.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,26 @@ def test_figure_label():
150150
with pytest.raises(ValueError):
151151
plt.figure(Figure())
152152

153+
def test_figure_label_replaced():
154+
plt.close('all')
155+
fig = plt.figure(1)
156+
with pytest.warns(mpl.MatplotlibDeprecationWarning,
157+
match="Changing 'Figure.number' is deprecated"):
158+
fig.number = 2
159+
assert fig.number == 2
160+
161+
def test_figure_no_label():
162+
# standalone figures do not have a figure attribute
163+
fig = matplotlib.figure.Figure()
164+
with pytest.raises(AttributeError):
165+
fig.number
166+
# but one can set one
167+
fig.number = 5
168+
with pytest.warns(MatplotlibDeprecationWarning,
169+
match="Changing 'Figure.number' is deprecated"):
170+
assert fig.number == 5
171+
# even though it's not known by pyplot
172+
assert not plt.fignum_exists(fig.number)
153173

154174
def test_fignum_exists():
155175
# pyplot figure creation, selection and closing with fignum_exists

0 commit comments

Comments
 (0)