Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added a find_all method to the RcParams dictionary. #1861

Merged
merged 3 commits into from

6 participants

@pelson
Collaborator

I find I want to search for RcParams by keyword quite frequently.

Now you can do:

>>> import matplotlib
>>> print '\n'.join(matplotlib.rcParams.find_all('font').keys())
font.cursive
font.family
font.fantasy
font.monospace
font.sans-serif
font.serif
font.size
font.stretch
font.style
font.variant
font.weight
legend.fontsize
mathtext.fontset
pdf.fonttype
pdf.use14corefonts
pgf.rcfonts
ps.fonttype
svg.fonttype

@mdboom
Owner

Cool feature. :+1: from me.

@efiring
Owner

@pelson, while you are at it, you might add a method (maybe with a pyplot function), perhaps "show_all(...)", which would use this selection to return a nicely formatted string with the keys and their current values.

lib/matplotlib/__init__.py
@@ -751,6 +751,22 @@ def values(self):
Return values in order of sorted keys.
"""
return [self[k] for k in self.iterkeys()]
+
+ def find_all(self, key_contains):
+ """
+ Return the subset of this RcParams dictionary for which the given
+ ``key_contains`` string is found somewhere in the key.
+
+ .. note::
+
+ Changes to the returned dictionary are *not* propagated to
+ the parent RcParams dictionary.
+
+ """
+ return RcParams((key, value)
+ for key, value in self.items()
+ if key_contains in key)
@dmcdougall Collaborator

Should this be key_contains.lower(), and the doc string updated to make it clear the search is case insensitive? Here I am assuming all the rcparams are lower case, so correct me if I'm wrong.

@pelson Collaborator
pelson added a note

I've not done this as I've gone for the regular expression route. I didn't want to make it too magic, and the user can always lower the string they are passing themselves if they are doing it programatically...

Let me know if you really want this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dmcdougall
Collaborator

This is beautiful. This will save me from having to search through the matplotlibrc file by hand.

@pelson, while you are at it, you might add a method (maybe with a pyplot function), perhaps "show_all(...)", which would use this selection to return a nicely formatted string with the keys and their current values.

Also, in addition to @efiring's suggestion, might it be worth to have a search for pyplot functions, too? Note, this is just a thought that should be in a separate PR if a consensus is reached.

@pelson
Collaborator

@pelson, while you are at it, you might add a method (maybe with a pyplot function), perhaps "show_all(...)", which would use this selection to return a nicely formatted string with the keys and their current values.

Personally I wouldn't use the pyplot function and I'm a hesitant to add more stuff there if possible, but I'd certainly make use of a __repr__ to RcParams which wrapped http://docs.python.org/2/library/pprint.html#pprint.pprint

Something like:

>>> print matplotlib.rcParams.find_all('font')
RcParams({
         'font.size': ...,
         })

I'd also be happy to add a __str__ and __repr__.

@pelson
Collaborator

Ok, I've added some bells and whistles to this PR. I expect the new functionality means that the pyplot function is no longer required, but shout if you disagree @efiring.

Cheers,

lib/matplotlib/__init__.py
@@ -750,7 +761,25 @@ def values(self):
"""
Return values in order of sorted keys.
"""
- return [self[k] for k in self.iterkeys()]
+ return [self[k] for k in self.keys()]
+
+ def find_all(self, pattern):
+ """
+ Return the subset of this RcParams dictionary for which the given
+ ``pattern`` string is found, by :func:`re.search`, somewhere in the key.
@mdboom Owner
mdboom added a note

I find this a little bit unclear.

Maybe just say "pattern is a Python regular expression".

@pelson Collaborator
pelson added a note

Thanks @mdboom. I'll change to:

Return the subset of this RcParams dictionary whose keys match,
using :func:`re.search`, the given "pattern".

I'm happy to iterate on this though if you have further improvements?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pelson
Collaborator

Ok. I think that is all the actions taken care of. I think this is good to go.

@mdboom
Owner

Needs a rebase.

@pelson
Collaborator

Thanks @mdboom - done.

@ivanov
Collaborator

my initial reaction is against using regular expressions:

to quote @yesthatjwz:

Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.

I can't think of a time when I will want the full power of a regular expression for searching the rcParams, but I can imagine wanting to find all instances of something with a period in the name, and be annoyed every time that I have to escape it.

Edit:
The name find_all makes me think of string's find, so I intuitively just want it to do substring searching. But there are some advantages to use regex, in that it allows us to specify something like '^font' for only keys starting with font.

I have code like this for grabbing portions of rcParam keys so I can set them, or do something else with them:

In [10]: [k for k in matplotlib.rcParams if 'font' in k]
Out[10]: 
['font.fantasy',
 'svg.fonttype',
 'font.cursive',
 'mathtext.fontset',
 'font.serif',
 'font.stretch',
 'font.size',
 'ps.fonttype',
 'legend.fontsize',
 'font.variant',
 'pdf.fonttype',
 'pdf.use14corefonts',
 'font.style',
 'font.family',
 'pgf.rcfonts',
 'font.sans-serif',
 'font.weight',
 'font.monospace']

In [11]: [k for k in matplotlib.rcParams if k.startswith('font')]
Out[11]: 
['font.fantasy',
 'font.cursive',
 'font.serif',
 'font.stretch',
 'font.size',
 'font.variant',
 'font.style',
 'font.family',
 'font.sans-serif',
 'font.weight',
 'font.monospace']
lib/matplotlib/__init__.py
@@ -738,6 +738,17 @@ def __getitem__(self, key):
key = alt
return dict.__getitem__(self, key)
+ def __repr__(self):
+ import pprint
+ class_name = self.__class__.__name__
+ indent = len(class_name) + 1
+ repr_split = pprint.pformat(dict(self), indent=1, width=80 - indent).split('\n')
+ repr_indented = ('\n' + ' ' * indent).join(repr_split)
+ return '{}({})'.format(class_name, repr_indented)
@ivanov Collaborator
ivanov added a note

this style of formatting is not supported in python2.6

In [5]: '{}({})'.format('sorry', '>= 2.7 only')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-b84304fedd68> in <module>()
----> 1 '{}({})'.format('sorry', '>= 2.7 only')

ValueError: zero length field name in format
@pelson Collaborator
pelson added a note

Thanks @ivanov - travis spotted this one too - I'm always making this mistake! Bring on ending support for 2.6 :wink:

@ivanov Collaborator
ivanov added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/matplotlib/__init__.py
@@ -738,6 +738,17 @@ def __getitem__(self, key):
key = alt
return dict.__getitem__(self, key)
+ def __repr__(self):
+ import pprint
+ class_name = self.__class__.__name__
+ indent = len(class_name) + 1
+ repr_split = pprint.pformat(dict(self), 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('{}: {}'.format(k, v) for k, v in sorted(self.items()))
@ivanov Collaborator
ivanov added a note

same as above (>= 2.7 only)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@WeatherGod
Collaborator
@pelson
Collaborator

Agreed. I would be happy if we could just simply use the globbing syntax.

I'm not wed to any particular form. I can see arguments for and against all 3 of the proposed solutions (in, re.search and fnmatch.fnmatch). I only submitted this PR as I was sick of typing the (1 line) list comprehension to filter rcParams :smile: . To be honest, the most frequent use case for me would be to do rcParams.find_all('font'), I don't need re/glob searching etc. etc.. Obviously, no matter which we choose (and I really do not mind which that is), the option of writing your own comprehension is still fine if you have a more complex case that is not handled.

@ivanov
Collaborator

I'm not wed to any particular form.

well, this bikeshedding won't be very fun if you're always going to be so agreeable, Phil! ;)

I trust your decision, and do not feel strongly either way. My initial reaction was a worry that I'd have to write "font" or something like that, but that's not the case with any of the proposals on the table.

So just that formatting thing needs to be fixed, and if you want to switch the way the matching works, that too, but it's fine as is, too. And then it's :cake: + :fork_and_knife: for you @pelson

@pelson
Collaborator

My initial reaction was a worry that I'd have to write "font" or something like that, but that's not the case with any of the proposals on the table.

Sadly, this is the case with the globbing syntax, otherwise I would have gone with @WeatherGod's nice suggestion.
Therefore I have stuck with the regex approach (with the understanding that it will annoy @ivanov when searching for keys containing periods :unamused:).

And then it's :cake: + :fork_and_knife: for you @pelson

Nomnom... anyone care to merge :smile:

@ivanov ivanov merged commit bc510f3 into matplotlib:master
@ivanov
Collaborator

with the understanding that it will annoy @ivanov when searching for keys containing periods

Oh, I'll get over it... merged, thanks, @pelson. Now have some :coffee: with your :cake:

@dmcdougall dmcdougall commented on the diff
lib/matplotlib/__init__.py
@@ -706,8 +706,8 @@ class RcParams(dict):
:mod:`matplotlib.rcsetup`
"""
- validate = dict([ (key, converter) for key, (default, converter) in \
- defaultParams.iteritems() ])
+ validate = dict((key, converter) for key, (default, converter) in \
@dmcdougall Collaborator

I know this has been merged already, but I only just saw this trailing backslash.

@mdboom Owner
mdboom added a note

Thanks for finding. I have just fixed directly on 1.3.x in `
207469f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
15 doc/users/whats_new.rst
@@ -110,6 +110,21 @@ used, which, in the case of straight contours was sometimes quite distant
from the requested location. Much more precise label positioning is now
possible.
+Quickly find rcParams
+---------------------
+Phil Elson made it easier to search for rcParameters by passing a
+valid regular expression to :func:`matplotlib.RcParams.find_all`.
+:class:`matplotlib.RcParams` now also has a pretty repr and str representation
+so that search results are printed prettily:
+
+ >>> import matplotlib
+ >>> print(matplotlib.rcParams.find_all('\.size'))
+ RcParams({'font.size': 12,
+ 'xtick.major.size': 4,
+ 'xtick.minor.size': 2,
+ 'ytick.major.size': 4,
+ 'ytick.minor.size': 2})
+
.. _whats-new-1-2-2:
new in matplotlib 1.2.2
View
37 lib/matplotlib/__init__.py
@@ -706,8 +706,8 @@ class RcParams(dict):
:mod:`matplotlib.rcsetup`
"""
- validate = dict([ (key, converter) for key, (default, converter) in \
- defaultParams.iteritems() ])
+ validate = dict((key, converter) for key, (default, converter) in \
@dmcdougall Collaborator

I know this has been merged already, but I only just saw this trailing backslash.

@mdboom Owner
mdboom added a note

Thanks for finding. I have just fixed directly on 1.3.x in `
207469f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ defaultParams.iteritems())
msg_depr = "%s is deprecated and replaced with %s; please use the latter."
msg_depr_ignore = "%s is deprecated and ignored. Use %s"
@@ -738,6 +738,19 @@ def __getitem__(self, key):
key = alt
return dict.__getitem__(self, key)
+ def __repr__(self):
+ import pprint
+ class_name = self.__class__.__name__
+ indent = len(class_name) + 1
+ repr_split = pprint.pformat(dict(self), indent=1,
+ width=80 - indent).split('\n')
+ repr_indented = ('\n' + ' ' * indent).join(repr_split)
+ return '{0}({1})'.format(class_name, repr_indented)
+
+ def __str__(self):
+ return '\n'.join('{0}: {1}'.format(k, v)
+ for k, v in sorted(self.items()))
+
def keys(self):
"""
Return sorted list of keys.
@@ -750,7 +763,25 @@ def values(self):
"""
Return values in order of sorted keys.
"""
- return [self[k] for k in self.iterkeys()]
+ return [self[k] for k in self.keys()]
+
+ def find_all(self, pattern):
+ """
+ Return the subset of this RcParams dictionary whose keys match,
+ using :func:`re.search`, the given ``pattern``.
+
+ .. note::
+
+ Changes to the returned dictionary are *not* propagated to
+ the parent RcParams dictionary.
+
+ """
+ import re
+ pattern_re = re.compile(pattern)
+ return RcParams((key, value)
+ for key, value in self.items()
+ if pattern_re.search(key))
+
def rc_params(fail_on_error=False):
'Return the default params updated from the values in the rc file'
View
20 lib/matplotlib/tests/__init__.py
@@ -1,8 +1,12 @@
from __future__ import print_function
from matplotlib import rcParams, rcdefaults, use
+import difflib
+
+
_multiprocess_can_split_ = True
+
def setup():
use('Agg', warn=False) # use Agg backend for these tests
@@ -13,3 +17,19 @@ def setup():
rcParams['font.family'] = 'Bitstream Vera Sans'
rcParams['text.hinting'] = False
rcParams['text.hinting_factor'] = 8
+
+
+def assert_str_equal(reference_str, test_str,
+ format_str='String {str1} and {str2} do not match:\n{differences}'):
+ """
+ Assert the two strings are equal. If not, fail and print their diffs using difflib.
+
+ """
+ if reference_str != test_str:
+ diff = difflib.unified_diff(reference_str.splitlines(1),
+ test_str.splitlines(1),
+ 'Reference', 'Test result',
+ '', '', 0)
+ raise ValueError(format_str.format(str1=reference_str,
+ str2=test_str,
+ differences=''.join(diff)))
View
39 lib/matplotlib/tests/test_rcparams.py
@@ -1,6 +1,9 @@
import os
import matplotlib as mpl
+from matplotlib.tests import assert_str_equal
+
+
mpl.rc('text', usetex=False)
mpl.rc('lines', linewidth=22)
@@ -34,5 +37,39 @@ def test_rcparams():
mpl.rcParams['lines.linewidth'] = linewidth
+def test_RcParams_class():
+ rc = mpl.RcParams({'font.cursive': ['Apple Chancery',
+ 'Textile',
+ 'Zapf Chancery',
+ 'cursive'],
+ 'font.family': 'sans-serif',
+ 'font.weight': 'normal',
+ 'font.size': 12})
+
+
+ expected_repr = """
+RcParams({'font.cursive': ['Apple Chancery',
+ 'Textile',
+ 'Zapf Chancery',
+ 'cursive'],
+ 'font.family': 'sans-serif',
+ 'font.size': 12,
+ 'font.weight': 'normal'})""".lstrip()
+
+ assert_str_equal(expected_repr, repr(rc))
+
+ expected_str = """
+font.cursive: ['Apple Chancery', 'Textile', 'Zapf Chancery', 'cursive']
+font.family: sans-serif
+font.size: 12
+font.weight: normal""".lstrip()
+
+ assert_str_equal(expected_str, str(rc))
+
+ # test the find_all functionality
+ assert ['font.cursive', 'font.size'] == sorted(rc.find_all('i[vz]').keys())
+ assert ['font.family'] == rc.find_all('family').keys()
+
if __name__ == '__main__':
- test_rcparams()
+ import nose
+ nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
Something went wrong with that request. Please try again.