diff --git a/doc/api/api_changes/2015-12-01-TAC.rst b/doc/api/api_changes/2015-12-01-TAC.rst new file mode 100644 index 000000000000..62ad44cec06e --- /dev/null +++ b/doc/api/api_changes/2015-12-01-TAC.rst @@ -0,0 +1,19 @@ +`Artist.update` has return value +```````````````````````````````` + +The methods `matplotlib.artist.Artist.set`, +`matplotlib.Artist.update`, and the function `matplotlib.artist.setp` +now use a common codepath to look up how to update the given artist +properties (either using the setter methods or an attribute/property). + +The behavior of `matplotlib.Artist.update` is slightly changed to +return a list of the returned values from the setter methods to avoid +changing the API of `matplotlib.Artist.set` and +`matplotlib.artist.setp`. + +The keys passed into `matplotlib.Artist.update` are now converted to +lower case before being processed to match the behavior of +`matplotlib.Artist.set` and `matplotlib.artist.setp`. This should not +break any user code because there are no set methods with capitals in +the names, however going forward this puts a constraint on naming +properties. diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index a4614f214299..60fc18929037 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -2,6 +2,7 @@ unicode_literals) from matplotlib.externals import six +from collections import OrderedDict import re import warnings @@ -82,6 +83,10 @@ class Artist(object): aname = 'Artist' zorder = 0 + # order of precedence when bulk setting/updating properties + # via update. The keys should be property names and the values + # integers + _prop_order = dict(color=-1) def __init__(self): self._stale = True @@ -843,23 +848,43 @@ def update(self, props): Update the properties of this :class:`Artist` from the dictionary *prop*. """ - store = self.eventson - self.eventson = False - changed = False - - for k, v in six.iteritems(props): - if k in ['axes']: - setattr(self, k, v) + def _update_property(self, k, v): + """sorting out how to update property (setter or setattr) + + Parameters + ---------- + k : str + The name of property to update + v : obj + The value to assign to the property + Returns + ------- + ret : obj or None + If using a `set_*` method return it's return, else None. + """ + k = k.lower() + # white list attributes we want to be able to update through + # art.update, art.set, setp + if k in {'axes'}: + return setattr(self, k, v) else: func = getattr(self, 'set_' + k, None) if func is None or not six.callable(func): raise AttributeError('Unknown property %s' % k) - func(v) - changed = True - self.eventson = store - if changed: + return func(v) + + store = self.eventson + self.eventson = False + try: + ret = [_update_property(self, k, v) + for k, v in props.items()] + finally: + self.eventson = store + + if len(ret): self.pchanged() self.stale = True + return ret def get_label(self): """ @@ -919,23 +944,13 @@ def properties(self): return ArtistInspector(self).properties() def set(self, **kwargs): + """A property batch setter. Pass *kwargs* to set properties. """ - A property batch setter. Pass *kwargs* to set properties. - Will handle property name collisions (e.g., if both - 'color' and 'facecolor' are specified, the property - with higher priority gets set last). + props = OrderedDict( + sorted(kwargs.items(), reverse=True, + key=lambda x: (self._prop_order.get(x[0], 0), x[0]))) - """ - ret = [] - for k, v in sorted(kwargs.items(), reverse=True): - k = k.lower() - funcName = "set_%s" % k - func = getattr(self, funcName, None) - if func is None: - raise TypeError('There is no %s property "%s"' % - (self.__class__.__name__, k)) - ret.extend([func(v)]) - return ret + return self.update(props) def findobj(self, match=None, include_self=True): """ @@ -1445,26 +1460,18 @@ def setp(obj, *args, **kwargs): if not cbook.iterable(obj): objs = [obj] else: - objs = cbook.flatten(obj) + objs = list(cbook.flatten(obj)) if len(args) % 2: raise ValueError('The set args must be string, value pairs') - funcvals = [] + # put args into ordereddict to maintain order + funcvals = OrderedDict() for i in range(0, len(args) - 1, 2): - funcvals.append((args[i], args[i + 1])) - funcvals.extend(sorted(kwargs.items(), reverse=True)) - - ret = [] - for o in objs: - for s, val in funcvals: - s = s.lower() - funcName = "set_%s" % s - func = getattr(o, funcName, None) - if func is None: - raise TypeError('There is no %s property "%s"' % - (o.__class__.__name__, s)) - ret.extend([func(val)]) + funcvals[args[i]] = args[i + 1] + + ret = [o.update(funcvals) for o in objs] + ret.extend([o.set(**kwargs) for o in objs]) return [x for x in cbook.flatten(ret)]