Skip to content

Commit 01ceeef

Browse files
committed
MNT: Don't rely on RcParams being a dict subclass in internal code
Eventually, we want to be able to remove the dict subclassing from RcParams, which will allow better initialization and handling. We've publically announced that people should not rely on dict in https://matplotlib.org/stable/api/prev_api_changes/api_changes_3.7.0.html#rcparams-type This is an internal cleanup step to remove the dict-assumption and a preparation for further refactoring.
1 parent 7a2ea1c commit 01ceeef

File tree

3 files changed

+41
-16
lines changed

3 files changed

+41
-16
lines changed

lib/matplotlib/__init__.py

+35-13
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,35 @@ def _get(self, key):
712712
"""
713713
return dict.__getitem__(self, key)
714714

715+
def _update_raw(self, other_params):
716+
"""
717+
Directly update the data from *other_params*, bypassing deprecation,
718+
backend and validation logic on both sides.
719+
720+
This ``rcParams._update_raw(params)`` replaces the previous pattern
721+
``dict.update(rcParams, params)``.
722+
723+
Parameters
724+
----------
725+
other_params : dict or `.RcParams`
726+
The input mapping from which to update.
727+
"""
728+
if isinstance(other_params, RcParams):
729+
other_params = dict.items(other_params)
730+
dict.update(self, other_params)
731+
732+
def _ensure_has_backend(self):
733+
"""
734+
Ensure that a "backend" entry exists.
735+
736+
Normally, the default matplotlibrc file contains *no* entry for "backend" (the
737+
corresponding line starts with ##, not #; we fill in _auto_backend_sentinel
738+
in that case. However, packagers can set a different default backend
739+
(resulting in a normal `#backend: foo` line) in which case we should *not*
740+
fill in _auto_backend_sentinel.
741+
"""
742+
dict.setdefault(self, "backend", rcsetup._auto_backend_sentinel)
743+
715744
def __setitem__(self, key, val):
716745
try:
717746
if key in _deprecated_map:
@@ -961,24 +990,17 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True):
961990
return config
962991

963992

964-
# When constructing the global instances, we need to perform certain updates
965-
# by explicitly calling the superclass (dict.update, dict.items) to avoid
966-
# triggering resolution of _auto_backend_sentinel.
967993
rcParamsDefault = _rc_params_in_file(
968994
cbook._get_data_path("matplotlibrc"),
969995
# Strip leading comment.
970996
transform=lambda line: line[1:] if line.startswith("#") else line,
971997
fail_on_error=True)
972-
dict.update(rcParamsDefault, rcsetup._hardcoded_defaults)
973-
# Normally, the default matplotlibrc file contains *no* entry for backend (the
974-
# corresponding line starts with ##, not #; we fill on _auto_backend_sentinel
975-
# in that case. However, packagers can set a different default backend
976-
# (resulting in a normal `#backend: foo` line) in which case we should *not*
977-
# fill in _auto_backend_sentinel.
978-
dict.setdefault(rcParamsDefault, "backend", rcsetup._auto_backend_sentinel)
998+
rcParamsDefault._update_raw(rcsetup._hardcoded_defaults)
999+
rcParamsDefault._ensure_has_backend()
1000+
9791001
rcParams = RcParams() # The global instance.
980-
dict.update(rcParams, dict.items(rcParamsDefault))
981-
dict.update(rcParams, _rc_params_in_file(matplotlib_fname()))
1002+
rcParams._update_raw(rcParamsDefault)
1003+
rcParams._update_raw(_rc_params_in_file(matplotlib_fname()))
9821004
rcParamsOrig = rcParams.copy()
9831005
with _api.suppress_matplotlib_deprecation_warning():
9841006
# This also checks that all rcParams are indeed listed in the template.
@@ -1190,7 +1212,7 @@ def rc_context(rc=None, fname=None):
11901212
rcParams.update(rc)
11911213
yield
11921214
finally:
1193-
dict.update(rcParams, orig) # Revert to the original rcs.
1215+
rcParams._update_raw(orig) # Revert to the original rcs.
11941216

11951217

11961218
def use(backend, *, force=True):

lib/matplotlib/__init__.pyi

+4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ class RcParams(dict[str, Any]):
7070
def __init__(self, *args, **kwargs) -> None: ...
7171
def _set(self, key: str, val: Any) -> None: ...
7272
def _get(self, key: str) -> Any: ...
73+
74+
def _update_raw(self, other_params: dict | RcParams) -> None: ...
75+
76+
def _ensure_has_backend(self) -> None: ...
7377
def __setitem__(self, key: str, val: Any) -> None: ...
7478
def __getitem__(self, key: str) -> Any: ...
7579
def __iter__(self) -> Generator[str, None, None]: ...

lib/matplotlib/pyplot.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,7 @@ def switch_backend(newbackend: str) -> None:
410410
switch_backend("agg")
411411
rcParamsOrig["backend"] = "agg"
412412
return
413-
# have to escape the switch on access logic
414-
old_backend = dict.__getitem__(rcParams, 'backend')
413+
old_backend = rcParams._get('backend') # get without triggering backend resolution
415414

416415
module = backend_registry.load_backend_module(newbackend)
417416
canvas_class = module.FigureCanvas
@@ -841,7 +840,7 @@ def xkcd(
841840
"xkcd mode is not compatible with text.usetex = True")
842841

843842
stack = ExitStack()
844-
stack.callback(dict.update, rcParams, rcParams.copy()) # type: ignore[arg-type]
843+
stack.callback(rcParams._update_raw, rcParams.copy()) # type: ignore[arg-type]
845844

846845
from matplotlib import patheffects
847846
rcParams.update({

0 commit comments

Comments
 (0)