8000 Clarify error message for bad keyword arguments. · matplotlib/matplotlib@97830da · GitHub
[go: up one dir, main page]

Skip to content

Commit 97830da

Browse files
committed
Clarify error message for bad keyword arguments.
`plot([], [], foo=42)` previously emitted ``` 'Line2D' object has no property 'foo' ``` which refers to the Matplotlib-specific concept of "properties". It now instead emits ``` Line2D.set() got an unexpected keyword argument 'foo' ``` which is modeled after the standard error message for unknown keyword arguments. (To maximize backcompat, the implementation goes through a new _internal_update, which does *not* error when the same prop is passed under different aliases. This could be changed later, but is not the goal of this PR.)
1 parent c0c3627 commit 97830da

File tree

12 files changed

+74
-55
lines changed

12 files changed

+74
-55
lines changed

lib/matplotlib/artist.py

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,32 +1048,6 @@ def set_in_layout(self, in_layout):
10481048
"""
10491049
self._in_layout = in_layout
10501050

1051-
def update(self, props):
1052-
"""
1053-
Update this artist's properties from the dict *props*.
1054-
1055-
Parameters
1056-
----------
1057-
props : dict
1058-
"""
1059-
ret = []
1060-
with cbook._setattr_cm(self, eventson=False):
1061-
for k, v in props.items():
1062-
# Allow attributes we want to be able to update through
1063-
# art.update, art.set, setp.
1064-
if k == "axes":
1065-
ret.append(setattr(self, k, v))
1066-
else:
1067-
func = getattr(self, f"set_{k}", None)
1068-
if not callable(func):
1069-
raise AttributeError(f"{type(self).__name__!r} object "
1070-
f"has no property {k!r}")
1071-
ret.append(func(v))
1072-
if ret:
1073-
self.pchanged()
1074-
self.stale = True
1075-
return ret
1076-
10771051
def get_label(self):
10781052
"""Return the label used for this artist in the legend."""
10791053
return self._label
@@ -1161,12 +1135,57 @@ def properties(self):
11611135
"""Return a dictionary of all the properties of the artist."""
11621136
return ArtistInspector(self).properties()
11631137

1138+
def _update_props(self, props, *, errfmt):
1139+
"""
1140+
Helper for `.Artist.set` and `.Artist.update`.
1141+
1142+
*errfmt* is used to generate error messages for invalid property
1143+
names; it get formatted with ``type(self)`` and the property name.
1144+
"""
1145+
ret = []
1146+
with cbook._setattr_cm(self, eventson=False):
1147+
for k, v in props.items():
1148+
# Allow attributes we want to be able to update through
1149+
# art.update, art.set, setp.
1150+
if k == "axes":
1151+
ret.append(setattr(self, k, v))
1152+
else:
1153+
func = getattr(self, f"set_{k}", None)
1154+
if not callable(func):
1155+
raise AttributeError(errfmt.format(type(self), k))
1156+
ret.append(func(v))
1157+
if ret:
1158+
self.pchanged()
1159+
self.stale = True
1160+
return ret
1161+
1162+
def update(self, props):
1163+
"""
1164+
Update this artist's properties from the dict *props*.
1165+
1166+
Parameters
1167+
----------
1168+
props : dict
1169+
"""
1170+
return self._update_props(
1171+
props, errfmt="{.__name__!r} object has no property {!r}")
1172+
1173+
def _internal_update(self, kwargs):
1174+
"""
1175+
Update artist properties without prenormalizing them, but generating
1176+
errors as if calling `set`.
1177+
1178+
The lack of prenormalization is to maintain backcompatibility.
1179+
"""
1180+
return self._update_props(
1181+
kwargs,
1182+
errfmt="{.__name__}.set() got an unexpected keyword argument {!r}")
1183+
11641184
def set(self, **kwargs):
11651185
# docstring and signature are auto-generated via
11661186
# Artist._update_set_signature_and_docstring() at the end of the
11671187
# module.
1168-
kwargs = cbook.normalize_kwargs(kwargs, self)
1169-
return self.update(kwargs)
1188+
return self._internal_update(cbook.normalize_kwargs(kwargs, self))
11701189

11711190
@contextlib.contextmanager
11721191
def _cm_set(self, **kwargs):

lib/matplotlib/axes/_axes.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def set_title(self, label, fontdict=None, loc=None, pad=None, *, y=None,
166166
title.update(default)
167167
if fontdict is not None:
168168
title.update(fontdict)
169-
title.update(kwargs)
169+
title._internal_update(kwargs)
170170
return title
171171

172172
def get_legend_handles_labels(self, legend_handler_map=None):
@@ -1064,7 +1064,7 @@ def hlines(self, y, xmin, xmax, colors=None, linestyles='solid',
10641064
lines = mcoll.LineCollection(masked_verts, colors=colors,
10651065
linestyles=linestyles, label=label)
10661066
self.add_collection(lines, autolim=False)
1067-
lines.update(kwargs)
1067+
lines._internal_update(kwargs)
10681068

10691069
if len(y) > 0:
10701070
minx = min(xmin.min(), xmax.min())
@@ -1143,7 +1143,7 @@ def vlines(self, x, ymin, ymax, colors=None, linestyles='solid',
11431143
lines = mcoll.LineCollection(masked_verts, colors=colors,
11441144
linestyles=linestyles, label=label)
11451145
self.add_collection(lines, autolim=False)
1146-
lines.update(kwargs)
1146+
lines._internal_update(kwargs)
11471147

11481148
if len(x) > 0:
11491149
minx = x.min()
@@ -1363,7 +1363,7 @@ def eventplot(self, positions, orientation='horizontal', lineoffsets=1,
13631363
color=color,
13641364
linestyle=linestyle)
13651365
self.add_collection(coll, autolim=False)
1366-
coll.update(kwargs)
1366+
coll._internal_update(kwargs)
13671367
colls.append(coll)
13681368

13691369
if len(positions) > 0:
@@ -2410,7 +2410,7 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
24102410
label='_nolegend_',
24112411
hatch=htch,
24122412
)
2413-
r.update(kwargs)
2413+
r._internal_update(kwargs)
24142414
r.get_path()._interpolation_steps = 100
24152415
if orientation == 'vertical':
24162416
r.sticky_edges.y.append(b)
@@ -4502,7 +4502,7 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None,
45024502
collection.set_cmap(cmap)
45034503
collection.set_norm(norm)
45044504
collection._scale_norm(norm, vmin, vmax)
4505-
collection.update(kwargs)
4505+
collection._internal_update(kwargs)
45064506

45074507
# Classic mode only:
45084508
# ensure there are margins to allow for the
@@ -4830,7 +4830,7 @@ def reduce_C_function(C: array) -> float
48304830
collection.set_cmap(cmap)
48314831
collection.set_norm(norm)
48324832
collection.set_alpha(alpha)
4833-
collection.update(kwargs)
4833+
collection._internal_update(kwargs)
48344834
collection._scale_norm(norm, vmin, vmax)
48354835

48364836
corners = ((xmin, ymin), (xmax, ymax))
@@ -4882,7 +4882,7 @@ def reduce_C_function(C: array) -> float
48824882
bar.set_cmap(cmap)
48834883
bar.set_norm(norm)
48844884
bar.set_alpha(alpha)
4885-
bar.update(kwargs)
4885+
bar._internal_update(kwargs)
48864886
bars.append(self.add_collection(bar, autolim=False))
48874887

48884888
collection.hbar, collection.vbar = bars
@@ -6763,11 +6763,11 @@ def hist(self, x, bins=None, range=None, density=False, weights=None,
67636763
for patch, lbl in itertools.zip_longest(patches, labels):
67646764
if patch:
67656765
p = patch[0]
6766-
p.update(kwargs)
6766+
p._internal_update(kwargs)
67676767
if lbl is not None:
67686768
p.set_label(lbl)
67696769
for p in patch[1:]:
6770-
p.update(kwargs)
6770+
p._internal_update(kwargs)
67716771
p.set_label('_nolegend_')
67726772

67736773
if nx == 1:

lib/matplotlib/axes/_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ def __init__(self, fig, rect,
642642
if yscale:
643643
self.set_yscale(yscale)
644644

645-
self.update(kwargs)
645+
self._internal_update(kwargs)
646646

647647
for name, axis in self._get_axis_map().items():
648648
axis.callbacks._pickled_cids.add(

lib/matplotlib/axis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,10 +1818,10 @@ def set_ticklabels(self, ticklabels, *, minor=False, **kwargs):
18181818
tick_label = formatter(loc, pos)
18191819
# deal with label1
18201820
tick.label1.set_text(tick_label)
1821-
tick.label1.update(kwargs)
1821+
tick.label1._internal_update(kwargs)
18221822
# deal with label2
18231823
tick.label2.set_text(tick_label)
1824-
tick.label2.update(kwargs)
1824+
tick.label2._internal_update(kwargs)
18251825
# only return visible tick labels
18261826
if tick.label1.get_visible():
18271827
ret.append(tick.label1)

lib/matplotlib/collections.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def __init__(self,
206206
self._offset_transform = offset_transform
207207

208208
self._path_effects = None
209-
self.update(kwargs)
209+
self._internal_update(kwargs)
210210
self._paths = None
211211

212212
def get_paths(self):

lib/matplotlib/image.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def __init__(self, ax,
273273

274274
self._imcache = None
275275

276-
self.update(kwargs)
276+
self._internal_update(kwargs)
277277

278278
def __getstate__(self):
279279
# Save some space on the pickle by not saving the cache.
@@ -1215,7 +1215,7 @@ def __init__(self, ax,
12151215
**kwargs : `.Artist` properties
12161216
"""
12171217
super().__init__(ax, norm=norm, cmap=cmap)
1218-
self.update(kwargs)
1218+
self._internal_update(kwargs)
12191219
if A is not None:
12201220
self.set_data(x, y, A)
12211221

@@ -1356,7 +1356,7 @@ def __init__(self, fig,
13561356
self.figure = fig
13571357
self.ox = offsetx
13581358
self.oy = offsety
1359-
self.update(kwargs)
1359+
self._internal_update(kwargs)
13601360
self.magnification = 1.0
13611361

13621362
def get_extent(self):

lib/matplotlib/lines.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def __init__(self, xdata, ydata,
391391

392392
# update kwargs before updating data to give the caller a
393393
# chance to init axes (and hence unit support)
394-
self.update(kwargs)
394+
self._internal_update(kwargs)
395395
self.pickradius = pickradius
396396
self.ind_offset = 0
397397
if (isinstance(self._picker, Number) and

lib/matplotlib/offsetbox.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ class OffsetBox(martist.Artist):
193193
"""
194194
def __init__(self, *args, **kwargs):
195195
super().__init__(*args)
196-
self.update(kwargs)
196+
self._internal_update(kwargs)
197197
# Clipping has not been implemented in the OffsetBox family, so
198198
# disable the clip flag for consistency. It can always be turned back
199199
# on to zero effect.
@@ -1359,7 +1359,7 @@ def __init__(self, offsetbox, xy,
13591359
if bboxprops:
13601360
self.patch.set(**bboxprops)
13611361

1362-
self.update(kwargs)
1362+
self._internal_update(kwargs)
13631363

13641364
@property
13651365
def xyann(self):

lib/matplotlib/patches.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def __init__(self,
111111
self.set_joinstyle(joinstyle)
112112

113113
if len(kwargs):
114-
self.update(kwargs)
114+
self._internal_update(kwargs)
115115

116116
def get_verts(self):
117117
"""

lib/matplotlib/projections/polar.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,7 @@ def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs):
13071307
elif fmt is not None:
13081308
self.xaxis.set_major_formatter(mticker.FormatStrFormatter(fmt))
13091309
for t in self.xaxis.get_ticklabels():
1310-
t.update(kwargs)
1310+
t._internal_update(kwargs)
13111311
return self.xaxis.get_ticklines(), self.xaxis.get_ticklabels()
13121312

13131313
def set_rgrids(self, radii, labels=None, angle=None, fmt=None, **kwargs):
@@ -1362,7 +1362,7 @@ def set_rgrids(self, radii, labels=None, angle=None, fmt=None, **kwargs):
13621362
angle = self.get_rlabel_position()
13631363
self.set_rlabel_position(angle)
13641364
for t in self.yaxis.get_ticklabels():
1365-
t.update(kwargs)
1365+
t._internal_update(kwargs)
13661366
return self.yaxis.get_gridlines(), self.yaxis.get_ticklabels()
13671367

13681368
def format_coord(self, theta, r):

lib/matplotlib/pyplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,7 +1789,7 @@ def xticks(ticks=None, labels=None, **kwargs):
17891789
if labels is None:
17901790
labels = ax.get_xticklabels()
17911791
for l in labels:
1792-
l.update(kwargs)
1792+
l._internal_update(kwargs)
17931793
else:
17941794
labels = ax.set_xticklabels(labels, **kwargs)
17951795

@@ -1849,7 +1849,7 @@ def yticks(ticks=None, labels=None, **kwargs):
18491849
if labels is None:
18501850
labels = ax.get_yticklabels()
18511851
for l in labels:
1852-
l.update(kwargs)
1852+
l._internal_update(kwargs)
18531853
else:
18541854
labels = ax.set_yticklabels(labels, **kwargs)
18551855

lib/matplotlib/table.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def set_text_props(self, **kwargs):
184184
185185
%(Text:kwdoc)s
186186
"""
187-
self._text.update(kwargs)
187+
self._text._internal_update(kwargs)
188188
self.stale = True
189189

190190
@property
@@ -315,7 +315,7 @@ def __init__(self, ax, loc=None, bbox=None, **kwargs):
315315
self._edges = None
316316
self._autoColumns = []
317317
self._autoFontsize = True
318-
self.update(kwargs)
318+
self._internal_update(kwargs)
319319

320320
self.set_clip_on(False)
321321

0 commit comments

Comments
 (0)
0