Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 92825fe

Browse files
authored
Merge pull request #20569 from timhoffm/axes-set
Better signature and docstring for Artist.set
2 parents e996212 + 2867517 commit 92825fe

File tree

4 files changed

+116
-1
lines changed

4 files changed

+116
-1
lines changed

doc/api/axes_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,3 +606,4 @@ Other
606606
Axes.get_default_bbox_extra_artists
607607
Axes.get_transformed_clip_path_and_affine
608608
Axes.has_data
609+
Axes.set

doc/api/axis_api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ XAxis Specific
181181
XAxis.get_text_heights
182182
XAxis.get_ticks_position
183183
XAxis.set_ticks_position
184+
XAxis.set_label_position
184185
XAxis.tick_bottom
185186
XAxis.tick_top
186187

@@ -197,6 +198,7 @@ YAxis Specific
197198
YAxis.get_ticks_position
198199
YAxis.set_offset_position
199200
YAxis.set_ticks_position
201+
YAxis.set_label_position
200202
YAxis.tick_left
201203
YAxis.tick_right
202204

@@ -264,4 +266,5 @@ specify a matching series of labels. Calling ``set_ticks`` makes a
264266
Tick.set_label1
265267
Tick.set_label2
266268
Tick.set_pad
269+
Tick.set_url
267270
Tick.update_position

lib/matplotlib/artist.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import contextlib
33
from functools import wraps
44
import inspect
5+
from inspect import Signature, Parameter
56
import logging
67
from numbers import Number
78
import re
@@ -84,6 +85,12 @@ def _stale_axes_callback(self, val):
8485
_XYPair = namedtuple("_XYPair", "x y")
8586

8687

88+
class _Unset:
89+
def __repr__(self):
90+
return "<UNSET>"
91+
_UNSET = _Unset()
92+
93+
8794
class Artist:
8895
"""
8996
Abstract base class for objects that render into a FigureCanvas.
@@ -93,6 +100,51 @@ class Artist:
93100

94101
zorder = 0
95102

103+
def __init_subclass__(cls):
104+
# Inject custom set() methods into the subclass with signature and
105+
# docstring based on the subclasses' properties.
106+
107+
if not hasattr(cls.set, '_autogenerated_signature'):
108+
# Don't overwrite cls.set if the subclass or one of its parents
109+
# has defined a set method set itself.
110+
# If there was no explicit definition, cls.set is inherited from
111+
# the hierarchy of auto-generated set methods, which hold the
112+
# flag _autogenerated_signature.
113+
return
114+
115+
cls.set = lambda self, **kwargs: Artist.set(self, **kwargs)
116+
cls.set.__name__ = "set"
117+
cls.set.__qualname__ = f"{cls.__qualname__}.set"
118+
cls._update_set_signature_and_docstring()
119+
120+
_PROPERTIES_EXCLUDED_FROM_SET = [
121+
'navigate_mode', # not a user-facing function
122+
'figure', # changing the figure is such a profound operation
123+
# that we don't want this in set()
124+
'3d_properties', # cannot be used as a keyword due to leading digit
125+
]
126+
127+
@classmethod
128+
def _update_set_signature_and_docstring(cls):
129+
"""
130+
Update the signature of the set function to list all properties
131+
as keyword arguments.
132+
133+
Property aliases are not listed in the signature for brevity, but
134+
are still accepted as keyword arguments.
135+
"""
136+
cls.set.__signature__ = Signature(
137+
[Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
138+
*[Parameter(prop, Parameter.KEYWORD_ONLY, default=_UNSET)
139+
for prop in ArtistInspector(cls).get_setters()
140+
if prop not in Artist._PROPERTIES_EXCLUDED_FROM_SET]])
141+
cls.set._autogenerated_signature = True
142+
143+
cls.set.__doc__ = (
144+
"Set multiple properties at once.\n\n"
145+
"Supported properties are\n\n"
146+
+ kwdoc(cls))
147+
96148
def __init__(self):
97149
self._stale = True
98150
self.stale_callback = None
@@ -1096,7 +1148,9 @@ def properties(self):
10961148
return ArtistInspector(self).properties()
10971149

10981150
def set(self, **kwargs):
1099-
"""A property batch setter. Pass *kwargs* to set properties."""
1151+
# docstring and signature are auto-generated via
1152+
# Artist._update_set_signature_and_docstring() at the end of the
1153+
# module.
11001154
kwargs = cbook.normalize_kwargs(kwargs, self)
11011155
return self.update(kwargs)
11021156

@@ -1385,6 +1439,21 @@ def aliased_name(self, s):
13851439
aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, [])))
13861440
return s + aliases
13871441

1442+
_NOT_LINKABLE = {
1443+
# A set of property setter methods that are not available in our
1444+
# current docs. This is a workaround used to prevent trying to link
1445+
# these setters which would lead to "target reference not found"
1446+
# warnings during doc build.
1447+
'matplotlib.image._ImageBase.set_alpha',
1448+
'matplotlib.image._ImageBase.set_array',
1449+
'matplotlib.image._ImageBase.set_data',
1450+
'matplotlib.image._ImageBase.set_filternorm',
1451+
'matplotlib.image._ImageBase.set_filterrad',
1452+
'matplotlib.image._ImageBase.set_interpolation',
1453+
'matplotlib.image._ImageBase.set_resample',
1454+
'matplotlib.text._AnnotationBase.set_annotation_clip',
1455+
}
1456+
13881457
def aliased_name_rest(self, s, target):
13891458
"""
13901459
Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME',
@@ -1394,6 +1463,10 @@ def aliased_name_rest(self, s, target):
13941463
alias, return 'markerfacecolor or mfc' and for the transform
13951464
property, which does not, return 'transform'.
13961465
"""
1466+
# workaround to prevent "reference target not found"
1467+
if target in self._NOT_LINKABLE:
1468+
return f'``{s}``'
1469+
13971470
aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, [])))
13981471
return ':meth:`%s <%s>`%s' % (s, target, aliases)
13991472

@@ -1656,3 +1729,7 @@ def kwdoc(artist):
16561729
return ('\n'.join(ai.pprint_setters_rest(leadingspace=4))
16571730
if mpl.rcParams['docstring.hardcopy'] else
16581731
'Properties:\n' + '\n'.join(ai.pprint_setters(leadingspace=4)))
1732+
1733+
# We defer this to the end of them module, because it needs ArtistInspector
1734+
# to be defined.
1735+
Artist._update_set_signature_and_docstring()

lib/matplotlib/tests/test_artist.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,37 @@ def func(artist):
340340
art.remove_callback(oid)
341341
art.pchanged() # must not call the callback anymore
342342
assert func.counter == 2
343+
344+
345+
def test_set_signature():
346+
"""Test autogenerated ``set()`` for Artist subclasses."""
347+
class MyArtist1(martist.Artist):
348+
def set_myparam1(self, val):
349+
pass
350+
351+
assert hasattr(MyArtist1.set, '_autogenerated_signature')
352+
assert 'myparam1' in MyArtist1.set.__doc__
353+
354+
class MyArtist2(MyArtist1):
355+
def set_myparam2(self, val):
356+
pass
357+
358+
assert hasattr(MyArtist2.set, '_autogenerated_signature')
359+
assert 'myparam1' in MyArtist2.set.__doc__
360+
assert 'myparam2' in MyArtist2.set.__doc__
361+
362+
363+
def test_set_is_overwritten():
364+
"""set() defined in Artist subclasses should not be overwritten."""
365+
class MyArtist3(martist.Artist):
366+
367+
def set(self, **kwargs):
368+
"""Not overwritten."""
369+
370+
assert not hasattr(MyArtist3.set, '_autogenerated_signature')
371+
assert MyArtist3.set.__doc__ == "Not overwritten."
372+
373+
class MyArtist4(MyArtist3):
374+
pass
375+
376+
assert MyArtist4.set is MyArtist3.set

0 commit comments

Comments
 (0)