-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
RcParams should not inherit from dict #12577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,41 +852,51 @@ 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( | ||
"3.0", "{} is deprecated; in the future, examples will be " | ||
"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'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. underscore to not make this a publically accessible global |
||
# 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 | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually don't think we should allow del'ing items from the rcParams as that has no sense. It may not have been worth it to prevent that with the old implementation, but now it's just a matter of replacing the implementation by a raise TypeError.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right. The current code prevents it, because we did not reimplement the
MutableMapping.__delitem__
. Before we were just inheriting fromdict
, so del'ing should have been possible. InheritingMutableMapping
was introduced within a large commit #7549. I dare assuming that both behaviors were incidental and nobody really thought about it.I'm extending the RcParams in my private code to be able to style and style-context some of my plot elements in specialized plots. While I'm currently not del'ing anything I could imagine that there's a use-case for that. Maybe we should prevent del'ing only for values that are in the defaults.
But as a first step it's ok to stay as restrictive as we currently are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talking about custom extensions to rcParams (none of which are really using the public API I guess), I realized that this PR would break https://github.com/anntzer/mplcairo#antialiasing where I use
dict.__setitem__
to bypass the validation code on input (to set lines.antialiased to one of the many cairo antialiasing modes, whereas the validator just accepts True/False).I guess that's related to your point in #12577 (comment)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping nobody would mess with the implementation details of
RcParams
in such a way except our own code. Ok, then we have to take it a step slower, define an official API for that and only later migrate to theMutableMapping
.By the way, What was your reason for introducing the multiple inheritance in the first place? IMO deriving from
dict
is not good but still far better than deriving fromdict
andMutableMapping
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be fully consistent with my own general approach wrt backcompat ("we should consider whether it'll actually matter to enough people that we reject a backcompat break"): I'm not going to throw a fit if the API changes here, and having a "more official" way to bypass the validator would be better anyways.
When I wrote it, it seemed to be a good way to provide update() cheaply.
In fact, looking at it again:
dict
, keeping most of the old code, and then doupdate = MutableMapping.update
(thus easily stealing the implementation from MutableMapping without pulling in the whole multiple inheritance problem, and not breaking my approach in mplcairo :p);MutableMapping.__len__
, is a CPython bug: https://bugs.python.org/issue35063There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM