From 3399dc4b1ea51a14fda31bdc315528f775192d58 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 20 Oct 2018 23:41:01 +0200 Subject: [PATCH] RcParams should not inherit from dict --- lib/matplotlib/__init__.py | 52 +++++++++++++++++----------- lib/matplotlib/backends/qt_compat.py | 20 +++++------ lib/matplotlib/cbook/__init__.py | 2 +- lib/matplotlib/pyplot.py | 4 +-- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index f504673776af..bb4f926a26d8 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -117,6 +117,7 @@ import atexit from collections.abc import MutableMapping import contextlib +import copy import distutils.version import functools import importlib @@ -759,10 +760,9 @@ def gen_candidates(): _all_deprecated = {*_deprecated_map, *_deprecated_ignore_map} -class RcParams(MutableMapping, dict): - +class RcParams(MutableMapping): """ - A dictionary object including validation + A dictionary object including validation. validating functions are defined and associated with rc parameters in :mod:`matplotlib.rcsetup` @@ -803,6 +803,7 @@ def msg_backend_obsolete(self): # validate values on the way in def __init__(self, *args, **kwargs): + self._data = {} self.update(*args, **kwargs) def __setitem__(self, key, val): @@ -840,7 +841,7 @@ def __setitem__(self, key, val): cval = self.validate[key](val) except ValueError as ve: raise ValueError("Key %s: %s" % (key, str(ve))) - dict.__setitem__(self, key, cval) + self._data[key] = cval except KeyError: raise KeyError( '%s is not a valid rc parameter. See rcParams.keys() for a ' @@ -851,13 +852,13 @@ def __getitem__(self, key): version, alt_key, alt_val, inverse_alt = _deprecated_map[key] cbook.warn_deprecated( version, key, obj_type="rcparam", alternative=alt_key) - return inverse_alt(dict.__getitem__(self, alt_key)) + return inverse_alt(self._data[alt_key]) elif key in _deprecated_ignore_map: version, alt_key = _deprecated_ignore_map[key] cbook.warn_deprecated( version, key, obj_type="rcparam", alternative=alt_key) - return dict.__getitem__(self, alt_key) if alt_key else None + return self._data[alt_key] if alt_key else None elif key == 'examples.directory': cbook.warn_deprecated( @@ -865,27 +866,37 @@ def __getitem__(self, key): "found relative to the 'datapath' directory.".format(key)) elif key == "backend": - val = dict.__getitem__(self, key) + val = self._data[key] if val is rcsetup._auto_backend_sentinel: from matplotlib import pyplot as plt plt.switch_backend(rcsetup._auto_backend_sentinel) - return dict.__getitem__(self, key) + return self._data[key] def __repr__(self): class_name = self.__class__.__name__ indent = len(class_name) + 1 - repr_split = pprint.pformat(dict(self), indent=1, + repr_split = pprint.pformat(self._data, indent=1, width=80 - indent).split('\n') repr_indented = ('\n' + ' ' * indent).join(repr_split) return '{}({})'.format(class_name, repr_indented) def __str__(self): - return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items()))) + return '\n'.join(map('{0[0]}: {0[1]}'.format, + sorted(self._data.items()))) def __iter__(self): """Yield sorted list of keys.""" - yield from sorted(dict.__iter__(self)) + yield from sorted(self._data) + + def __len__(self): + return len(self._data) + + def __delitem__(self, key): + del self._data[key] + + def copy(self): + return copy.deepcopy(self) def find_all(self, pattern): """ @@ -900,7 +911,7 @@ def find_all(self, pattern): """ pattern_re = re.compile(pattern) return RcParams((key, value) - for key, value in self.items() + for key, value in self._data.items() if pattern_re.search(key)) @@ -1060,24 +1071,23 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): rcParams = rc_params() # Don't trigger deprecation warning when just fetching. -if dict.__getitem__(rcParams, 'examples.directory'): +with warnings.catch_warnings(): + warnings.simplefilter("ignore", MatplotlibDeprecationWarning) + examples_directory = rcParams['examples.directory'] # paths that are intended to be relative to matplotlib_fname() # are allowed for the examples.directory parameter. # However, we will need to fully qualify the path because # Sphinx requires absolute paths. - if not os.path.isabs(rcParams['examples.directory']): + if examples_directory and not os.path.isabs(examples_directory): _basedir, _fname = os.path.split(matplotlib_fname()) # Sometimes matplotlib_fname() can return relative paths, # Also, using realpath() guarantees that Sphinx will use # the same path that matplotlib sees (in case of weird symlinks). _basedir = os.path.realpath(_basedir) - _fullpath = os.path.join(_basedir, rcParams['examples.directory']) + _fullpath = os.path.join(_basedir, examples_directory) rcParams['examples.directory'] = _fullpath - -with warnings.catch_warnings(): - warnings.simplefilter("ignore", MatplotlibDeprecationWarning) - rcParamsOrig = RcParams(rcParams.copy()) + rcParamsOrig = RcParams(rcParams._data.copy()) rcParamsDefault = RcParams([(key, default) for key, (default, converter) in defaultParams.items() if key not in _all_deprecated]) @@ -1275,7 +1285,7 @@ def __init__(self, rc=None, fname=None): def __fallback(self): # If anything goes wrong, revert to the original rcs. updated_backend = self._orig['backend'] - dict.update(rcParams, self._orig) + rcParams._data.update(self._orig._data) # except for the backend. If the context block triggered resolving # the auto backend resolution keep that value around if self._orig['backend'] is rcsetup._auto_backend_sentinel: @@ -1328,7 +1338,7 @@ def use(arg, warn=True, force=False): name = validate_backend(arg) # if setting back to the same thing, do nothing - if (dict.__getitem__(rcParams, 'backend') == name): + if (rcParams._data['backend'] == name): pass # Check if we have already imported pyplot and triggered diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index b0fa0a907371..8e4993497d29 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -33,31 +33,31 @@ # First, check if anything is already imported. if "PyQt5" in sys.modules: QT_API = QT_API_PYQT5 - dict.__setitem__(rcParams, "backend.qt5", QT_API) + rcParams._data["backend.qt5"] = QT_API elif "PySide2" in sys.modules: QT_API = QT_API_PYSIDE2 - dict.__setitem__(rcParams, "backend.qt5", QT_API) + rcParams._data["backend.qt5"] = QT_API elif "PyQt4" in sys.modules: QT_API = QT_API_PYQTv2 - dict.__setitem__(rcParams, "backend.qt4", QT_API) + rcParams._data["backend.qt4"] = QT_API elif "PySide" in sys.modules: QT_API = QT_API_PYSIDE - dict.__setitem__(rcParams, "backend.qt4", QT_API) + rcParams._data["backend.qt4"] = QT_API # Otherwise, check the QT_API environment variable (from Enthought). This can # only override the binding, not the backend (in other words, we check that the # requested backend actually matches). elif rcParams["backend"] in ["Qt5Agg", "Qt5Cairo"]: if QT_API_ENV == "pyqt5": - dict.__setitem__(rcParams, "backend.qt5", QT_API_PYQT5) + rcParams._data["backend.qt5"] = QT_API_PYQT5 elif QT_API_ENV == "pyside2": - dict.__setitem__(rcParams, "backend.qt5", QT_API_PYSIDE2) - QT_API = dict.__getitem__(rcParams, "backend.qt5") + rcParams._data["backend.qt5"] = QT_API_PYSIDE2 + QT_API = rcParams._data["backend.qt5"] elif rcParams["backend"] in ["Qt4Agg", "Qt4Cairo"]: if QT_API_ENV == "pyqt4": - dict.__setitem__(rcParams, "backend.qt4", QT_API_PYQTv2) + rcParams._data["backend.qt4"] = QT_API_PYQTv2 elif QT_API_ENV == "pyside": - dict.__setitem__(rcParams, "backend.qt4", QT_API_PYSIDE) - QT_API = dict.__getitem__(rcParams, "backend.qt4") + rcParams._data["backend.qt4"] = QT_API_PYSIDE + QT_API = rcParams._data["backend.qt4"] # A non-Qt backend was selected but we still got there (possible, e.g., when # fully manually embedding Matplotlib in a Qt app without using pyplot). else: diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 473c2324a7a7..874735f4efd4 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -445,7 +445,7 @@ def get_sample_data(fname, asfileobj=True): If the filename ends in .gz, the file is implicitly ungzipped. """ # Don't trigger deprecation warning when just fetching. - if dict.__getitem__(matplotlib.rcParams, 'examples.directory'): + if dict._data['examples.directory']: root = matplotlib.rcParams['examples.directory'] else: root = os.path.join(matplotlib._get_data_path(), 'sample_data') diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 6ceced49285c..a4323bfcd93f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2343,9 +2343,9 @@ def _autogen_docstring(base): # requested, ignore rcParams['backend'] and force selection of a backend that # is compatible with the current running interactive framework. if (rcParams["backend_fallback"] - and dict.__getitem__(rcParams, "backend") in _interactive_bk + and rcParams._data["backend"] in _interactive_bk and _get_running_interactive_framework()): - dict.__setitem__(rcParams, "backend", rcsetup._auto_backend_sentinel) + rcParams._data["backend"] = rcsetup._auto_backend_sentinel # Set up the backend. switch_backend(rcParams["backend"])