Skip to content

Commit c48414a

Browse files
committed
New version of unpack_labeled_data
1 parent 4059b44 commit c48414a

File tree

3 files changed

+326
-33
lines changed

3 files changed

+326
-33
lines changed

lib/matplotlib/__init__.py

+100-30
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
from itertools import chain
109109

110110
import io
111+
import inspect
111112
import locale
112113
import os
113114
import re
@@ -1528,58 +1529,127 @@ def _replacer(data, key):
15281529
return key
15291530

15301531

1531-
def unpack_labeled_data(wl_args=None, wl_kwargs=None, label_pos=None):
1532+
def unpack_labeled_data(replace_names=None, label_namer="y", positional_parameter_names=None):
15321533
"""
15331534
A decorator to add a 'data' kwarg to any a function. The signature
1534-
of the input function must be ::
1535+
of the input function must include the ax argument at the first position ::
15351536
15361537
def foo(ax, *args, **kwargs)
15371538
15381539
so this is suitable for use with Axes methods.
1539-
"""
1540-
if label_pos is not None:
1541-
label_arg, label_kwarg = label_pos
15421540
1543-
if wl_kwargs is not None:
1544-
wl_kwargs = set(wl_kwargs)
1545-
if wl_args is not None:
1546-
wl_args = set(wl_args)
1541+
Parameters
1542+
----------
1543+
replace_names : list of strings, optional, default: None
1544+
The list of parameter names which arguments should be replaced by `data[name]`. If None,
1545+
all arguments are replaced if they are included in `data`.
1546+
label_namer : string, optional, default: 'y'
1547+
The name of the parameter which argument should be used as label, if label is not set. If
1548+
None, the label keyword argument is not set.
1549+
positional_parameter_names : list of strings, optional, default: None
1550+
The full list of positional parameter names (including the `ax` argument at the first place
1551+
and including all possible positional parameter in `*args`), in the right order. Can also
1552+
include all other keyword parameter. Only needed if the wrapped function does contain
1553+
`*args` and replace_names is not None.
1554+
"""
1555+
if replace_names is not None:
1556+
replace_names = set(replace_names)
15471557

15481558
def param(func):
1559+
# this call is deprecated in 3.x and will be removed in py3.6 :-(
1560+
# TODO: implement the same for py 3.x/3.6+ with inspect.signature(..)
1561+
arg_spec = inspect.getargspec(func)
1562+
_arg_names = arg_spec.args
1563+
_has_no_varargs = arg_spec.varargs is None
1564+
_has_no_varkwargs = arg_spec.keywords is None
1565+
1566+
# there can't be any positional arguments behind *args and no positional args can end up
1567+
# in **kwargs, so we only need to check for varargs:
1568+
# http://stupidpythonideas.blogspot.de/2013/08/arguments-and-parameters.html
1569+
if _has_no_varargs:
1570+
# remove the first "ax" arg
1571+
arg_names = _arg_names[1:]
1572+
else:
1573+
# in this case we need a supplied list of arguments or we need to replace all variables
1574+
# -> compile time check
1575+
if replace_names is None:
1576+
# all argnames should be replaced
1577+
arg_names = None
1578+
elif len(replace_names) == 0:
1579+
# No argnames should be replaced
1580+
arg_names = []
1581+
else:
1582+
assert not (positional_parameter_names is None), "Got replace_names and wrapped function uses *args, need positional_parameter_names!"
1583+
# remove ax arg
1584+
arg_names = positional_parameter_names[1:]
1585+
1586+
# compute the possible label_namer and label position in positional arguments
1587+
label_pos = 9999 # bigger than all "possible" argument lists
1588+
label_namer_pos = 9999 # bigger than all "possible" argument lists
1589+
if label_namer and arg_names and (label_namer in arg_names):
1590+
label_namer_pos = arg_names.index(label_namer)
1591+
if "label" in arg_names:
1592+
label_pos = arg_names.index("label")
1593+
1594+
# Check that in case we know the label_namer is set but it could happen that we
1595+
# can't find it the arg_names are empty, then make that clear here...
1596+
# Unfortunately the label_namer can be in **kwargs, which we can't detect here and which
1597+
# results in a non set label even if the user expected it :-(
1598+
ensure = label_namer and _has_no_varkwargs and ((not arg_names) or (label_namer not in arg_names))
1599+
assert not ensure, "label_namer can't be found without 'positional_parameter_names'"
1600+
15491601
@functools.wraps(func)
15501602
def inner(ax, *args, **kwargs):
15511603
data = kwargs.pop('data', None)
1604+
xlabel = None
15521605
if data is not None:
1553-
if wl_args is None:
1554-
new_args = tuple(_replacer(data, a) for a in args)
1606+
# save the current label_namer value so that it can be used as a label
1607+
if label_namer_pos < len(args):
1608+
xlabel = args[label_namer_pos]
15551609
else:
1556-
new_args = tuple(_replacer(data, a) if j in wl_args else a
1557-
for j, a in enumerate(args))
1610+
xlabel = kwargs.get(label_namer, None)
1611+
1612+
if not isinstance(xlabel, six.string_types):
1613+
xlabel = None
15581614

1559-
if wl_kwargs is None:
1560-
new_kwargs = dict((k, _replacer(data, v))
1561-
for k, v in six.iteritems(kwargs))
1615+
if replace_names is None:
1616+
# all should be replaced
1617+
args = tuple(_replacer(data, a) for j, a in enumerate(args))
1618+
kwargs = dict((k, _replacer(data, v)) for k, v in six.iteritems(kwargs))
15621619
else:
1563-
new_kwargs = dict(
1564-
(k, _replacer(data, v) if k in wl_kwargs else v)
1620+
# An arg is replaced if the arg_name of that position is in replace_names ...
1621+
if len(arg_names) < len(args):
1622+
raise RuntimeError("Got more args than function expects")
1623+
args = tuple(_replacer(data, a) if arg_names[j] in replace_names else a
1624+
for j, a in enumerate(args))
1625+
# ... or a kwarg of that name in replace_names
1626+
kwargs = dict((k, _replacer(data, v) if k in replace_names else v)
15651627
for k, v in six.iteritems(kwargs))
1566-
else:
1567-
new_args, new_kwargs = args, kwargs
15681628

1569-
if (label_pos is not None and ('label' not in kwargs or
1570-
kwargs['label'] is None)):
1571-
if len(args) > label_arg:
1629+
# replace the label if this func "wants" a label arg and the user didn't set one
1630+
# Note: if the usere puts in "label=None", it does *NOT* get replaced!
1631+
user_supplied_label = (
1632+
(len(args) >= label_pos) or # label is included in args
1633+
('label' in kwargs) # ... or in kwargs
1634+
)
1635+
if (label_namer and not user_supplied_label):
1636+
if label_namer_pos < len(args):
15721637
try:
1573-
kwargs['label'] = args[label_arg].name
1638+
kwargs['label'] = args[label_namer_pos].name
15741639
except AttributeError:
1575-
pass
1576-
elif label_kwarg in kwargs:
1640+
kwargs['label'] = xlabel
1641+
elif label_namer in kwargs:
15771642
try:
1578-
new_kwargs['label'] = kwargs[label_kwarg].name
1643+
kwargs['label'] = kwargs[label_namer].name
15791644
except AttributeError:
1580-
pass
1581-
1582-
return func(ax, *new_args, **new_kwargs)
1645+
kwargs['label'] = xlabel
1646+
else:
1647+
import warnings
1648+
msg = "Tried to set a label via parameter '%s' but couldn't find such an argument. \n"\
1649+
"(This is a programming error, please report to the matplotlib list!)"
1650+
warnings.warn(msg, RuntimeWarning, stacklevel=2)
1651+
#raise Exception()
1652+
return func(ax, *args, **kwargs)
15831653
return inner
15841654
return param
15851655

lib/matplotlib/axes/_axes.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
902902
self.autoscale_view(scaley=False)
903903
return p
904904

905-
@unpack_labeled_data(wl_args=[1, 2, 3], wl_kwargs=['y', 'xmin', 'xmax'])
905+
@unpack_labeled_data(replace_names=['y', 'xmin', 'xmax'])
906906
@docstring.dedent
907907
def hlines(self, y, xmin, xmax, colors='k', linestyles='solid',
908908
label='', **kwargs):
@@ -2383,8 +2383,7 @@ def stem(self, *args, **kwargs):
23832383

23842384
return stem_container
23852385

2386-
@unpack_labeled_data(wl_args=[1, 3, 4],
2387-
wl_kwargs=['x', 'labels', 'colors'])
2386+
@unpack_labeled_data(replace_names=['x', 'labels', 'colors'])
23882387
def pie(self, x, explode=None, labels=None, colors=None,
23892388
autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1,
23902389
startangle=None, radius=None, counterclock=True,

0 commit comments

Comments
 (0)