Skip to content

Commit ef70f40

Browse files
committed
Deprecate globals using module-level __getattr__.
This PR is mostly just to propose a pattern for defining module-level `__getattr__`s for deprecating globals. Relying on lru_cache rather than defining variables as `global` (see change in `__init__.py`) avoids having to repeat *twice* the variable name, and allows immediately returning its value without assigning it. It means later accesses will be very slightly slower (because they'll still go through the lru_cache layer), but that should hopefully be negligible, and will mostly concern deprecated variables anyways. One *could* consider adding a helper API in `_api.deprecation` that just provides the decoration with `@lru_cache` and generates the AttributeError message when needed, but that seems rather overkill. The change in deprecation.py allows one to skip `obj_name` and get a message like "foo is deprecated..." rather than "The foo is deprecated...".
1 parent 01a1c49 commit ef70f40

File tree

5 files changed

+74
-44
lines changed

5 files changed

+74
-44
lines changed

lib/matplotlib/__init__.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,15 @@ def _get_version():
176176
return _version.version
177177

178178

179+
@functools.lru_cache(None)
179180
def __getattr__(name):
180-
if name in ("__version__", "__version_info__"):
181-
global __version__ # cache it.
182-
__version__ = _get_version()
183-
global __version__info__ # cache it.
184-
__version_info__ = _parse_to_version_info(__version__)
185-
return __version__ if name == "__version__" else __version_info__
181+
if name == "__version__":
182+
return _get_version()
183+
elif name == "__version_info__":
184+
return _parse_to_version_info(__getattr__("__version__"))
185+
elif name == "URL_REGEX":
186+
_api.warn_deprecated("3.5", name=name)
187+
return re.compile(r'^http://|^https://|^ftp://|^file:')
186188
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
187189

188190

@@ -714,14 +716,10 @@ def rc_params(fail_on_error=False):
714716
return rc_params_from_file(matplotlib_fname(), fail_on_error)
715717

716718

717-
# Deprecated in Matplotlib 3.5.
718-
URL_REGEX = re.compile(r'^http://|^https://|^ftp://|^file:')
719-
720-
721719
@_api.deprecated("3.5")
722720
def is_url(filename):
723721
"""Return whether *filename* is an http, https, ftp, or file URL path."""
724-
return URL_REGEX.match(filename) is not None
722+
return __getattr__("URL_REGEX").match(filename) is not None
725723

726724

727725
@functools.lru_cache()

lib/matplotlib/_api/deprecation.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,11 @@ def _generate_deprecation_warning(
3737
removal = f"in {removal}" if removal else "two minor releases later"
3838
if not message:
3939
message = (
40-
"\nThe %(name)s %(obj_type)s"
40+
("\nThe %(name)s %(obj_type)s" if obj_type else "%(name)s")
4141
+ (" will be deprecated in a future version"
4242
if pending else
4343
(" was deprecated in Matplotlib %(since)s"
44-
+ (" and will be removed %(removal)s"
45-
if removal else
46-
"")))
44+
+ (" and will be removed %(removal)s" if removal else "")))
4745
+ "."
4846
+ (" Use %(alternative)s instead." if alternative else "")
4947
+ (" %(addendum)s" if addendum else ""))

lib/matplotlib/backends/backend_gtk3.py

+20-11
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,26 @@
3636
backend_version = "%s.%s.%s" % (
3737
Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version())
3838

39-
try:
40-
_display = Gdk.Display.get_default()
41-
cursord = { # deprecated in Matplotlib 3.5.
42-
Cursors.MOVE: Gdk.Cursor.new_from_name(_display, "move"),
43-
Cursors.HAND: Gdk.Cursor.new_from_name(_display, "pointer"),
44-
Cursors.POINTER: Gdk.Cursor.new_from_name(_display, "default"),
45-
Cursors.SELECT_REGION: Gdk.Cursor.new_from_name(_display, "crosshair"),
46-
Cursors.WAIT: Gdk.Cursor.new_from_name(_display, "wait"),
47-
}
48-
except TypeError as exc:
49-
cursord = {} # deprecated in Matplotlib 3.5.
39+
40+
@functools.lru_cache(None)
41+
def __getattr__(name):
42+
if name == "cursord":
43+
_api.warn_deprecated("3.5", name=name)
44+
try:
45+
new_cursor = functools.partial(
46+
Gdk.Cursor.new_from_name, Gdk.Display.get_default())
47+
return {
48+
Cursors.MOVE: new_cursor("move"),
49+
Cursors.HAND: new_cursor("pointer"),
50+
Cursors.POINTER: new_cursor("default"),
51+
Cursors.SELECT_REGION: new_cursor("crosshair"),
52+
Cursors.WAIT: new_cursor("wait"),
53+
}
54+
except TypeError as exc:
55+
return {}
56+
else:
57+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
58+
5059

5160
# Placeholder
5261
_application = None

lib/matplotlib/backends/backend_wx.py

+28-14
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,25 @@
4040
# for some info about screen dpi
4141
PIXELS_PER_INCH = 75
4242

43-
# Delay time for idle checks
44-
IDLE_DELAY = 5 # Documented as deprecated as of Matplotlib 3.1.
43+
44+
@functools.lru_cache(None)
45+
def __getattr__(name):
46+
if name == "IDLE_DELAY":
47+
_api.warn_deprecated("3.1", name=name)
48+
return 5
49+
elif name == "cursord":
50+
_api.warn_deprecated("3.5", name=name)
51+
return { # deprecated in Matplotlib 3.5.
52+
cursors.MOVE: wx.CURSOR_HAND,
53+
cursors.HAND: wx.CURSOR_HAND,
54+
cursors.POINTER: wx.CURSOR_ARROW,
55+
cursors.SELECT_REGION: wx.CURSOR_CROSS,
56+
cursors.WAIT: wx.CURSOR_WAIT,
57+
cursors.RESIZE_HORIZONTAL: wx.CURSOR_SIZEWE,
58+
cursors.RESIZE_VERTICAL: wx.CURSOR_SIZENS,
59+
}
60+
else:
61+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
4562

4663

4764
def error_msg_wx(msg, parent=None):
@@ -721,7 +738,15 @@ def _onKeyUp(self, event):
721738

722739
def set_cursor(self, cursor):
723740
# docstring inherited
724-
cursor = wx.Cursor(_api.check_getitem(cursord, cursor=cursor))
741+
cursor = wx.Cursor(_api.check_getitem({
742+
cursors.MOVE: wx.CURSOR_HAND,
743+
cursors.HAND: wx.CURSOR_HAND,
744+
cursors.POINTER: wx.CURSOR_ARROW,
745+
cursors.SELECT_REGION: wx.CURSOR_CROSS,
746+
cursors.WAIT: wx.CURSOR_WAIT,
747+
cursors.RESIZE_HORIZONTAL: wx.CURSOR_SIZEWE,
748+
cursors.RESIZE_VERTICAL: wx.CURSOR_SIZENS,
749+
}, cursor=cursor))
725750
self.SetCursor(cursor)
726751
self.Update()
727752

@@ -1049,17 +1074,6 @@ def _set_frame_icon(frame):
10491074
frame.SetIcons(bundle)
10501075

10511076

1052-
cursord = { # deprecated in Matplotlib 3.5.
1053-
cursors.MOVE: wx.CURSOR_HAND,
1054-
cursors.HAND: wx.CURSOR_HAND,
1055-
cursors.POINTER: wx.CURSOR_ARROW,
1056-
cursors.SELECT_REGION: wx.CURSOR_CROSS,
1057-
cursors.WAIT: wx.CURSOR_WAIT,
1058-
cursors.RESIZE_HORIZONTAL: wx.CURSOR_SIZEWE,
1059-
cursors.RESIZE_VERTICAL: wx.CURSOR_SIZENS,
1060-
}
1061-
1062-
10631077
class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar):
10641078
def __init__(self, canvas, coordinates=True):
10651079
wx.ToolBar.__init__(self, canvas.GetParent(), -1)

lib/matplotlib/colorbar.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"""
1313

1414
import copy
15+
import functools
1516
import logging
1617
import textwrap
1718

@@ -193,10 +194,20 @@
193194
textwrap.indent(_make_axes_other_param_doc, " "),
194195
_colormap_kw_doc))
195196

196-
# Deprecated since 3.4.
197-
colorbar_doc = docstring.interpd.params["colorbar_doc"]
198-
colormap_kw_doc = _colormap_kw_doc
199-
make_axes_kw_doc = _make_axes_param_doc + _make_axes_other_param_doc
197+
198+
@functools.lru_cache(None)
199+
def __getattr__(name):
200+
if name == "colorbar_doc":
201+
_api.warn_deprecated("3.4", name=name)
202+
return docstring.interpd.params["colorbar_doc"]
203+
elif name == "colormap_kw_doc":
204+
_api.warn_deprecated("3.4", name=name)
205+
return _colormap_kw_doc
206+
elif name == "make_axes_kw_doc":
207+
_api.warn_deprecated("3.4", name=name)
208+
return _make_axes_param_doc + _make_axes_other_param_doc
209+
else:
210+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
200211

201212

202213
def _set_ticks_on_axis_warn(*args, **kw):

0 commit comments

Comments
 (0)