|
108 | 108 | from itertools import chain
|
109 | 109 |
|
110 | 110 | import io
|
| 111 | +import inspect |
111 | 112 | import locale
|
112 | 113 | import os
|
113 | 114 | import re
|
@@ -1528,58 +1529,127 @@ def _replacer(data, key):
|
1528 | 1529 | return key
|
1529 | 1530 |
|
1530 | 1531 |
|
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): |
1532 | 1533 | """
|
1533 | 1534 | 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 :: |
1535 | 1536 |
|
1536 | 1537 | def foo(ax, *args, **kwargs)
|
1537 | 1538 |
|
1538 | 1539 | so this is suitable for use with Axes methods.
|
1539 |
| - """ |
1540 |
| - if label_pos is not None: |
1541 |
| - label_arg, label_kwarg = label_pos |
1542 | 1540 |
|
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) |
1547 | 1557 |
|
1548 | 1558 | 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 | + |
1549 | 1601 | @functools.wraps(func)
|
1550 | 1602 | def inner(ax, *args, **kwargs):
|
1551 | 1603 | data = kwargs.pop('data', None)
|
| 1604 | + xlabel = None |
1552 | 1605 | 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] |
1555 | 1609 | 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 |
1558 | 1614 |
|
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)) |
1562 | 1619 | 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) |
1565 | 1627 | for k, v in six.iteritems(kwargs))
|
1566 |
| - else: |
1567 |
| - new_args, new_kwargs = args, kwargs |
1568 | 1628 |
|
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): |
1572 | 1637 | try:
|
1573 |
| - kwargs['label'] = args[label_arg].name |
| 1638 | + kwargs['label'] = args[label_namer_pos].name |
1574 | 1639 | except AttributeError:
|
1575 |
| - pass |
1576 |
| - elif label_kwarg in kwargs: |
| 1640 | + kwargs['label'] = xlabel |
| 1641 | + elif label_namer in kwargs: |
1577 | 1642 | try:
|
1578 |
| - new_kwargs['label'] = kwargs[label_kwarg].name |
| 1643 | + kwargs['label'] = kwargs[label_namer].name |
1579 | 1644 | 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) |
1583 | 1653 | return inner
|
1584 | 1654 | return param
|
1585 | 1655 |
|
|
0 commit comments