@@ -1503,19 +1503,19 @@ def test(verbosity=None, coverage=False, switch_backend_warn=True,
1503
1503
test .__test__ = False # pytest: this function is not a test
1504
1504
1505
1505
1506
- def _replacer (data , key ):
1506
+ def _replacer (data , value ):
1507
1507
"""Either returns data[key] or passes data back. Also
1508
1508
converts input data to a sequence as needed.
1509
1509
"""
1510
- # if key isn't a string don't bother
1511
- if not isinstance (key , str ):
1512
- return key
1513
- # try to use __getitem__
1514
1510
try :
1515
- return sanitize_sequence (data [key ])
1516
- # key does not exist, silently fall back to key
1517
- except KeyError :
1518
- return key
1511
+ # if key isn't a string don't bother
1512
+ if isinstance (value , str ):
1513
+ # try to use __getitem__
1514
+ value = data [value ]
1515
+ except Exception :
1516
+ # key does not exist, silently fall back to key
1517
+ pass
1518
+ return sanitize_sequence (value )
1519
1519
1520
1520
1521
1521
_DATA_DOC_APPENDIX = """
@@ -1529,43 +1529,33 @@ def _replacer(data, key):
1529
1529
"""
1530
1530
1531
1531
1532
- def _add_data_doc (docstring , replace_names , replace_all_args ):
1532
+ def _add_data_doc (docstring , replace_names ):
1533
1533
"""Add documentation for a *data* field to the given docstring.
1534
1534
1535
1535
Parameters
1536
1536
----------
1537
1537
docstring : str
1538
1538
The input docstring.
1539
- replace_names : list of strings or None
1539
+ replace_names : List[str] or None
1540
1540
The list of parameter names which arguments should be replaced by
1541
- `data[name]`. If None, all arguments are replaced if they are
1542
- included in `data`.
1543
- replace_all_args : bool
1544
- If True, all arguments in *args get replaced, even if they are not
1545
- in replace_names.
1541
+ ``data[name]`` (if ``data[name]`` does not throw an exception). If
1542
+ None, replacement is attempted for all arguments.
1546
1543
1547
1544
Returns
1548
1545
-------
1549
1546
The augmented docstring.
1550
1547
"""
1551
- if docstring is None :
1552
- docstring = ''
1553
- else :
1554
- docstring = dedent (docstring )
1555
- _repl = ""
1556
- if replace_names is None :
1557
- _repl = "* All positional and all keyword arguments."
1558
- else :
1559
- if len (replace_names ) != 0 :
1560
- _repl = "* All arguments with the following names: '{names}'."
1561
- if replace_all_args :
1562
- _repl += "\n * All positional arguments."
1563
- _repl = _repl .format (names = "', '" .join (sorted (replace_names )))
1564
- return docstring + _DATA_DOC_APPENDIX .format (replaced = _repl )
1548
+ docstring = dedent (docstring ) if docstring is not None else ""
1549
+ repl = ("* All positional and all keyword arguments."
1550
+ if replace_names is None else
1551
+ ""
1552
+ if len (replace_names ) == 0 else
1553
+ "* All arguments with the following names: {}." .format (
1554
+ ", " .join (map (repr , sorted (replace_names )))))
1555
+ return docstring + _DATA_DOC_APPENDIX .format (replaced = repl )
1565
1556
1566
1557
1567
- def _preprocess_data (replace_names = None , replace_all_args = False ,
1568
- label_namer = None , positional_parameter_names = None ):
1558
+ def _preprocess_data (func = None , * , replace_names = None , label_namer = None ):
1569
1559
"""
1570
1560
A decorator to add a 'data' kwarg to any a function. The signature
1571
1561
of the input function must include the ax argument at the first position ::
@@ -1576,216 +1566,109 @@ def foo(ax, *args, **kwargs)
1576
1566
1577
1567
Parameters
1578
1568
----------
1579
- replace_names : list of strings , optional, default: None
1569
+ replace_names : List[str] or None , optional, default: None
1580
1570
The list of parameter names which arguments should be replaced by
1581
- `data[name]`. If None, all arguments are replaced if they are
1582
- included in `data`.
1583
- replace_all_args : bool, default: False
1584
- If True, all arguments in *args get replaced, even if they are not
1585
- in replace_names.
1571
+ ``data[name]`` (if ``data[name]`` does not throw an exception). If
1572
+ None, replacement is attempted for all arguments.
1586
1573
label_namer : string, optional, default: None
1587
1574
The name of the parameter which argument should be used as label, if
1588
1575
label is not set. If None, the label keyword argument is not set.
1589
- positional_parameter_names : list of strings or callable, optional
1590
- The full list of positional parameter names (excluding an explicit
1591
- `ax`/'self' argument at the first place and including all possible
1592
- positional parameter in `*args`), in the right order. Can also include
1593
- all other keyword parameter. Only needed if the wrapped function does
1594
- contain `*args` and (replace_names is not None or replace_all_args is
1595
- False). If it is a callable, it will be called with the actual
1596
- tuple of *args and the data and should return a list like
1597
- above.
1598
- NOTE: callables should only be used when the names and order of *args
1599
- can only be determined at runtime. Please use list of names
1600
- when the order and names of *args is clear before runtime!
1601
1576
1602
1577
.. note:: decorator also converts MappingView input data to list.
1603
1578
"""
1604
- if replace_names is not None :
1605
- replace_names = set (replace_names )
1606
1579
1607
- def param (func ):
1608
- sig = inspect .signature (func )
1609
- _has_varargs = False
1610
- _has_varkwargs = False
1611
- _arg_names = []
1612
- params = list (sig .parameters .values ())
1613
- for p in params :
1614
- if p .kind is Parameter .VAR_POSITIONAL :
1615
- _has_varargs = True
1616
- elif p .kind is Parameter .VAR_KEYWORD :
1617
- _has_varkwargs = True
1618
- else :
1619
- _arg_names .append (p .name )
1620
- data_param = Parameter ('data' , Parameter .KEYWORD_ONLY , default = None )
1621
- if _has_varkwargs :
1622
- params .insert (- 1 , data_param )
1623
- else :
1624
- params .append (data_param )
1625
- new_sig = sig .replace (parameters = params )
1626
- # Import-time check: do we have enough information to replace *args?
1627
- arg_names_at_runtime = False
1628
- # there can't be any positional arguments behind *args and no
1629
- # positional args can end up in **kwargs, so only *varargs make
1630
- # problems.
1631
- # http://stupidpythonideas.blogspot.de/2013/08/arguments-and-parameters.html
1632
- if not _has_varargs :
1633
- # all args are "named", so no problem
1634
- # remove the first "ax" / self arg
1635
- arg_names = _arg_names [1 :]
1580
+ if func is None :
1581
+ return functools .partial (
1582
+ _preprocess_data ,
1583
+ replace_names = replace_names , label_namer = label_namer )
1584
+
1585
+ sig = inspect .signature (func )
1586
+ varargs_name = None
1587
+ varkwargs_name = None
1588
+ arg_names = []
1589
+ params = list (sig .parameters .values ())
1590
+ for p in params :
1591
+ if p .kind is Parameter .VAR_POSITIONAL :
1592
+ varargs_name = p .name
1593
+ elif p .kind is Parameter .VAR_KEYWORD :
1594
+ varkwargs_name = p .name
1636
1595
else :
1637
- # Here we have "unnamed" variables and we need a way to determine
1638
- # whether to replace a arg or not
1639
- if replace_names is None :
1640
- # all argnames should be replaced
1641
- arg_names = None
1642
- elif len (replace_names ) == 0 :
1643
- # No argnames should be replaced
1644
- arg_names = []
1645
- elif len (_arg_names ) > 1 and (positional_parameter_names is None ):
1646
- # we got no manual parameter names but more than an 'ax' ...
1647
- if len (replace_names - set (_arg_names [1 :])) == 0 :
1648
- # all to be replaced arguments are in the list
1649
- arg_names = _arg_names [1 :]
1650
- else :
1651
- raise AssertionError (
1652
- "Got unknown 'replace_names' and wrapped function "
1653
- "{!r} uses '*args', need 'positional_parameter_names'"
1654
- .format (func .__name__ ))
1655
- else :
1656
- if positional_parameter_names is not None :
1657
- if callable (positional_parameter_names ):
1658
- # determined by the function at runtime
1659
- arg_names_at_runtime = True
1660
- # so that we don't compute the label_pos at import time
1661
- arg_names = []
1662
- else :
1663
- arg_names = positional_parameter_names
1664
- else :
1665
- if replace_all_args :
1666
- arg_names = []
1667
- else :
1668
- raise AssertionError (
1669
- "Got 'replace_names' and wrapped function {!r} "
1670
- "uses *args, need 'positional_parameter_names' or "
1671
- "'replace_all_args'" .format (func .__name__ ))
1672
-
1673
- # compute the possible label_namer and label position in positional
1674
- # arguments
1675
- label_pos = 9999 # bigger than all "possible" argument lists
1676
- label_namer_pos = 9999 # bigger than all "possible" argument lists
1677
- if (label_namer and # we actually want a label here ...
1678
- arg_names and # and we can determine a label in *args ...
1679
- label_namer in arg_names ): # and it is in *args
1680
- label_namer_pos = arg_names .index (label_namer )
1681
- if "label" in arg_names :
1682
- label_pos = arg_names .index ("label" )
1683
-
1684
- # Check the case we know a label_namer but we can't find it the
1685
- # arg_names... Unfortunately the label_namer can be in **kwargs,
1686
- # which we can't detect here and which results in a non-set label
1687
- # which might surprise the user :-(
1688
- if label_namer and not arg_names_at_runtime and not _has_varkwargs :
1689
- if not arg_names :
1690
- raise AssertionError (
1691
- "label_namer {!r} can't be found as the parameter without "
1692
- "'positional_parameter_names'" .format (label_namer ))
1693
- elif label_namer not in arg_names :
1694
- raise AssertionError (
1695
- "label_namer {!r} can't be found in the parameter names "
1696
- "(known argnames: %s)." .format (label_namer , arg_names ))
1697
- else :
1698
- # this is the case when the name is in arg_names
1699
- pass
1700
-
1701
- @functools .wraps (func )
1702
- def inner (ax , * args , ** kwargs ):
1703
- # this is needed because we want to change these values if
1704
- # arg_names_at_runtime==True, but python does not allow assigning
1705
- # to a variable in a outer scope. So use some new local ones and
1706
- # set them to the already computed values.
1707
- _label_pos = label_pos
1708
- _label_namer_pos = label_namer_pos
1709
- _arg_names = arg_names
1710
-
1711
- label = None
1596
+ arg_names .append (p .name )
1597
+ data_param = Parameter ("data" , Parameter .KEYWORD_ONLY , default = None )
1598
+ if varkwargs_name :
1599
+ params .insert (- 1 , data_param )
1600
+ else :
1601
+ params .append (data_param )
1602
+ new_sig = sig .replace (parameters = params )
1603
+ arg_names = arg_names [1 :] # remove the first "ax" / self arg
1712
1604
1713
- data = kwargs .pop ('data' , None )
1605
+ if replace_names is not None :
1606
+ replace_names = set (replace_names )
1714
1607
1715
- if data is None : # data validation
1716
- args = tuple (sanitize_sequence (a ) for a in args )
1717
- else :
1718
- if arg_names_at_runtime :
1719
- # update the information about replace names and
1720
- # label position
1721
- _arg_names = positional_parameter_names (args , data )
1722
- if (label_namer and # we actually want a label here ...
1723
- _arg_names and # and we can find a label in *args
1724
- (label_namer in _arg_names )): # and it is in *args
1725
- _label_namer_pos = _arg_names .index (label_namer )
1726
- if "label" in _arg_names :
1727
- _label_pos = arg_names .index ("label" )
1728
-
1729
- # save the current label_namer value so that it can be used as
1730
- # a label
1731
- if _label_namer_pos < len (args ):
1732
- label = args [_label_namer_pos ]
1733
- else :
1734
- label = kwargs .get (label_namer , None )
1735
- # ensure a string, as label can't be anything else
1736
- if not isinstance (label , str ):
1737
- label = None
1738
-
1739
- if (replace_names is None ) or (replace_all_args is True ):
1740
- # all should be replaced
1741
- args = tuple (_replacer (data , a ) for
1742
- j , a in enumerate (args ))
1743
- else :
1744
- # An arg is replaced if the arg_name of that position is
1745
- # in replace_names ...
1746
- if len (_arg_names ) < len (args ):
1747
- raise RuntimeError (
1748
- "Got more args than function expects" )
1749
- args = tuple (_replacer (data , a )
1750
- if _arg_names [j ] in replace_names else a
1751
- for j , a in enumerate (args ))
1608
+ assert (replace_names or set ()) <= set (arg_names ) or varkwargs_name , (
1609
+ "Matplotlib internal error: invalid replace_names ({!r}) for {!r}"
1610
+ .format (replace_names , func .__name__ ))
1611
+ assert label_namer is None or label_namer in arg_names or varkwargs_name , (
1612
+ "Matplotlib internal error: invalid label_namer ({!r}) for {!r}"
1613
+ .format (label_namer , func .__name__ ))
1614
+
1615
+ @functools .wraps (func )
1616
+ def inner (ax , * args , ** kwargs ):
1617
+ data = kwargs .pop ("data" , None )
1618
+ if data is None :
1619
+ return func (ax , * args , ** kwargs )
1752
1620
1621
+ bound = new_sig .bind (ax , * args , ** kwargs )
1622
+ needs_label = (label_namer
1623
+ and "label" not in bound .arguments
1624
+ and "label" not in bound .kwargs )
1625
+ auto_label = (bound .arguments .get (label_namer )
1626
+ or bound .kwargs .get (label_namer ))
1627
+ if not isinstance (auto_label , str ):
1628
+ auto_label = None
1629
+
1630
+ for k , v in bound .arguments .items ():
1631
+ if k == varkwargs_name :
1632
+ for k1 , v1 in v .items ():
1633
+ if replace_names is None or k1 in replace_names :
1634
+ v [k1 ] = _replacer (data , v1 )
1635
+ elif k == varargs_name :
1753
1636
if replace_names is None :
1754
- # replace all kwargs ...
1755
- kwargs = {k : _replacer (data , v ) for k , v in kwargs .items ()}
1756
- else :
1757
- # ... or only if a kwarg of that name is in replace_names
1758
- kwargs = {
1759
- k : _replacer (data , v ) if k in replace_names else v
1760
- for k , v in kwargs .items ()}
1761
-
1762
- # replace the label if this func "wants" a label arg and the user
1763
- # didn't set one. Note: if the user puts in "label=None", it does
1764
- # *NOT* get replaced!
1765
- user_supplied_label = (
1766
- len (args ) >= _label_pos or # label is included in args
1767
- 'label' in kwargs # ... or in kwargs
1768
- )
1769
- if label_namer and not user_supplied_label :
1770
- if _label_namer_pos < len (args ):
1771
- kwargs ['label' ] = get_label (args [_label_namer_pos ], label )
1772
- elif label_namer in kwargs :
1773
- kwargs ['label' ] = get_label (kwargs [label_namer ], label )
1637
+ bound .arguments [k ] = tuple (
1638
+ _replacer (data , v1 ) for v1 in v )
1639
+ else :
1640
+ if replace_names is None or k in replace_names :
1641
+ bound .arguments [k ] = _replacer (data , v )
1642
+
1643
+ bound .apply_defaults ()
1644
+ del bound .arguments ["data" ]
1645
+
1646
+ all_kwargs = {** bound .arguments , ** bound .kwargs }
1647
+ if needs_label :
1648
+ if label_namer not in all_kwargs :
1649
+ warnings .warn (
1650
+ "Tried to set a label via parameter %r in func %r but "
1651
+ "couldn't find such an argument.\n "
1652
+ "(This is a programming error, please report to "
1653
+ "the Matplotlib list!)" % (label_namer , func .__name__ ),
1654
+ RuntimeWarning , stacklevel = 2 )
1655
+ else :
1656
+ label = get_label (all_kwargs [label_namer ], auto_label )
1657
+ if "label" in arg_names :
1658
+ bound .arguments ["label" ] = label
1659
+ try :
1660
+ bound .arguments .move_to_end (varkwargs_name )
1661
+ except KeyError :
1662
+ pass
1774
1663
else :
1775
- warnings .warn (
1776
- "Tried to set a label via parameter %r in func %r but "
1777
- "couldn't find such an argument.\n "
1778
- "(This is a programming error, please report to "
1779
- "the Matplotlib list!)" % (label_namer , func .__name__ ),
1780
- RuntimeWarning , stacklevel = 2 )
1781
- return func (ax , * args , ** kwargs )
1664
+ bound .arguments .setdefault (
1665
+ varkwargs_name , {})["label" ] = label
1782
1666
1783
- inner .__doc__ = _add_data_doc (inner .__doc__ ,
1784
- replace_names , replace_all_args )
1785
- inner .__signature__ = new_sig
1786
- return inner
1667
+ return func (* bound .args , ** bound .kwargs )
1787
1668
1788
- return param
1669
+ inner .__doc__ = _add_data_doc (inner .__doc__ , replace_names )
1670
+ inner .__signature__ = new_sig
1671
+ return inner
1789
1672
1790
1673
_log .debug ('matplotlib version %s' , __version__ )
1791
1674
_log .debug ('interactive is %s' , is_interactive ())
0 commit comments