@@ -1458,6 +1458,7 @@ def tk_window_focus():
1458
1458
'matplotlib.tests.test_triangulation' ,
1459
1459
'matplotlib.tests.test_widgets' ,
1460
1460
'matplotlib.tests.test_cycles' ,
1461
+ 'matplotlib.tests.test_labeled_data_unpacking' ,
1461
1462
'matplotlib.sphinxext.tests.test_tinypages' ,
1462
1463
'mpl_toolkits.tests.test_mplot3d' ,
1463
1464
'mpl_toolkits.tests.test_axes_grid1' ,
@@ -1566,18 +1567,21 @@ def foo(ax, *args, **kwargs)
1566
1567
replace_all_args : bool, default: False
1567
1568
If True, all arguments in *args get replaced, even if they are not
1568
1569
in replace_names.
1569
- NOTE: this should be used only when the order of the names depends on
1570
- the number of *args.
1571
1570
label_namer : string, optional, default: None
1572
1571
The name of the parameter which argument should be used as label, if
1573
1572
label is not set. If None, the label keyword argument is not set.
1574
- positional_parameter_names : list of strings, optional, default: None
1573
+ positional_parameter_names : list of strings or callable, optional
1575
1574
The full list of positional parameter names (excluding an explicit
1576
1575
`ax`/'self' argument at the first place and including all possible
1577
1576
positional parameter in `*args`), in the right order. Can also include
1578
1577
all other keyword parameter. Only needed if the wrapped function does
1579
1578
contain `*args` and (replace_names is not None or replace_all_args is
1580
- False).
1579
+ False). If it is a callable, it will be called with the actual
1580
+ tuple of *args and the data and should return a list like
1581
+ above.
1582
+ NOTE: callables should only be used when the names and order of *args
1583
+ can only be determined at runtime. Please use list of names
1584
+ when the order and names of *args is clear before runtime!
1581
1585
"""
1582
1586
if replace_names is not None :
1583
1587
replace_names = set (replace_names )
@@ -1590,16 +1594,19 @@ def param(func):
1590
1594
_has_no_varargs = arg_spec .varargs is None
1591
1595
_has_varkwargs = arg_spec .keywords is not None
1592
1596
1597
+ # Import-time check: do we have enough information to replace *args?
1598
+ arg_names_at_runtime = False
1593
1599
# there can't be any positional arguments behind *args and no
1594
- # positional args can end up in **kwargs, so we only need to check for
1595
- # varargs:
1600
+ # positional args can end up in **kwargs, so only *varargs make
1601
+ # problems.
1596
1602
# http://stupidpythonideas.blogspot.de/2013/08/arguments-and-parameters.html
1597
1603
if _has_no_varargs :
1604
+ # all args are "named", so no problem
1598
1605
# remove the first "ax" / self arg
1599
1606
arg_names = _arg_names [1 :]
1600
1607
else :
1601
- # in this case we need a supplied list of arguments or we need to
1602
- # replace all variables -> compile time check
1608
+ # Here we have "unnamed" variables and we need a way to determine
1609
+ # whether to replace a arg or not
1603
1610
if replace_names is None :
1604
1611
# all argnames should be replaced
1605
1612
arg_names = None
@@ -1618,7 +1625,13 @@ def param(func):
1618
1625
raise AssertionError (msg % func .__name__ )
1619
1626
else :
1620
1627
if positional_parameter_names is not None :
1621
- arg_names = positional_parameter_names
1628
+ if callable (positional_parameter_names ):
1629
+ # determined by the function at runtime
1630
+ arg_names_at_runtime = True
1631
+ # so that we don't compute the label_pos at import time
1632
+ arg_names = []
1633
+ else :
1634
+ arg_names = positional_parameter_names
1622
1635
else :
1623
1636
if replace_all_args :
1624
1637
arg_names = []
@@ -1633,7 +1646,9 @@ def param(func):
1633
1646
# arguments
1634
1647
label_pos = 9999 # bigger than all "possible" argument lists
1635
1648
label_namer_pos = 9999 # bigger than all "possible" argument lists
1636
- if label_namer and arg_names and (label_namer in arg_names ):
1649
+ if (label_namer # we actually want a label here ...
1650
+ and arg_names # and we can determine a label in *args ...
1651
+ and (label_namer in arg_names )): # and it is in *args
1637
1652
label_namer_pos = arg_names .index (label_namer )
1638
1653
if "label" in arg_names :
1639
1654
label_pos = arg_names .index ("label" )
@@ -1642,7 +1657,7 @@ def param(func):
1642
1657
# arg_names... Unfortunately the label_namer can be in **kwargs,
1643
1658
# which we can't detect here and which results in a non-set label
1644
1659
# which might surprise the user :-(
1645
- if label_namer and not _has_varkwargs :
1660
+ if label_namer and not arg_names_at_runtime and not _has_varkwargs :
1646
1661
if not arg_names :
1647
1662
msg = ("label_namer '%s' can't be found as the parameter "
1648
1663
"without 'positional_parameter_names'." )
@@ -1657,53 +1672,74 @@ def param(func):
1657
1672
1658
1673
@functools .wraps (func )
1659
1674
def inner (ax , * args , ** kwargs ):
1660
- data = kwargs .pop ('data' , None )
1675
+ # this is needed because we want to change these values if
1676
+ # arg_names_at_runtime==True, but python does not allow assigning
1677
+ # to a variable in a outer scope. So use some new local ones and
1678
+ # set them to the already computed values.
1679
+ _label_pos = label_pos
1680
+ _label_namer_pos = label_namer_pos
1681
+ _arg_names = arg_names
1682
+
1661
1683
label = None
1684
+
1685
+ data = kwargs .pop ('data' , None )
1662
1686
if data is not None :
1687
+ if arg_names_at_runtime :
1688
+ # update the information about replace names and
1689
+ # label position
1690
+ _arg_names = positional_parameter_names (args , data )
1691
+ if (label_namer # we actually want a label here ...
1692
+ and _arg_names # and we can find a label in *args ...
1693
+ and (label_namer in _arg_names )): # and it is in *args
1694
+ _label_namer_pos = _arg_names .index (label_namer )
1695
+ if "label" in _arg_names :
1696
+ _label_pos = arg_names .index ("label" )
1697
+
1663
1698
# save the current label_namer value so that it can be used as
1664
1699
# a label
1665
- if label_namer_pos < len (args ):
1666
- label = args [label_namer_pos ]
1700
+ if _label_namer_pos < len (args ):
1701
+ label = args [_label_namer_pos ]
1667
1702
else :
1668
1703
label = kwargs .get (label_namer , None )
1669
1704
# ensure a string, as label can't be anything else
1670
1705
if not isinstance (label , six .string_types ):
1671
1706
label = None
1672
1707
1673
- if (replace_names is None ) or replace_all_args :
1708
+ if (replace_names is None ) or ( replace_all_args is True ) :
1674
1709
# all should be replaced
1675
1710
args = tuple (_replacer (data , a ) for
1676
1711
j , a in enumerate (args ))
1677
1712
else :
1678
- # An arg is replaced if the arg_name of that position is in
1679
- # replace_names ...
1680
- if len (arg_names ) < len (args ):
1713
+ # An arg is replaced if the arg_name of that position is
1714
+ # in replace_names ...
1715
+ if len (_arg_names ) < len (args ):
1681
1716
raise RuntimeError (
1682
1717
"Got more args than function expects" )
1683
1718
args = tuple (_replacer (data , a )
1684
- if arg_names [j ] in replace_names else a
1719
+ if _arg_names [j ] in replace_names else a
1685
1720
for j , a in enumerate (args ))
1686
1721
1687
1722
if replace_names is None :
1723
+ # replace all kwargs ...
1688
1724
kwargs = dict ((k , _replacer (data , v ))
1689
1725
for k , v in six .iteritems (kwargs ))
1690
1726
else :
1691
- # ... or a kwarg of that name in replace_names
1727
+ # ... or only if a kwarg of that name is in replace_names
1692
1728
kwargs = dict ((k , _replacer (data , v )
1693
1729
if k in replace_names else v )
1694
1730
for k , v in six .iteritems (kwargs ))
1695
1731
1696
1732
# replace the label if this func "wants" a label arg and the user
1697
- # didn't set one Note: if the usere puts in "label=None", it does
1733
+ # didn't set one. Note: if the user puts in "label=None", it does
1698
1734
# *NOT* get replaced!
1699
1735
user_supplied_label = (
1700
- (len (args ) >= label_pos ) or # label is included in args
1736
+ (len (args ) >= _label_pos ) or # label is included in args
1701
1737
('label' in kwargs ) # ... or in kwargs
1702
1738
)
1703
1739
if (label_namer and not user_supplied_label ):
1704
- if label_namer_pos < len (args ):
1740
+ if _label_namer_pos < len (args ):
1705
1741
try :
1706
- kwargs ['label' ] = args [label_namer_pos ].name
1742
+ kwargs ['label' ] = args [_label_namer_pos ].name
1707
1743
except AttributeError :
1708
1744
kwargs ['label' ] = label
1709
1745
elif label_namer in kwargs :
@@ -1740,6 +1776,7 @@ def inner(ax, *args, **kwargs):
1740
1776
return inner
1741
1777
return param
1742
1778
1779
+
1743
1780
verbose .report ('matplotlib version %s' % __version__ )
1744
1781
verbose .report ('verbose.level %s' % verbose .level )
1745
1782
verbose .report ('interactive is %s' % is_interactive ())
0 commit comments