@@ -1503,19 +1503,19 @@ def test(verbosity=None, coverage=False, switch_backend_warn=True,
15031503test .__test__ = False # pytest: this function is not a test
15041504
15051505
1506- def _replacer (data ,key ):
1506+ def _replacer (data ,value ):
15071507"""Either returns data[key] or passes data back. Also
15081508 converts input data to a sequence as needed.
15091509 """
1510- # if key isn't a string don't bother
1511- if not isinstance (key ,str ):
1512- return key
1513- # try to use __getitem__
15141510try :
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 )
15191519
15201520
15211521_DATA_DOC_APPENDIX = """
@@ -1529,43 +1529,33 @@ def _replacer(data, key):
15291529"""
15301530
15311531
1532- def _add_data_doc (docstring ,replace_names , replace_all_args ):
1532+ def _add_data_doc (docstring ,replace_names ):
15331533"""Add documentation for a *data* field to the given docstring.
15341534
15351535 Parameters
15361536 ----------
15371537 docstring : str
15381538 The input docstring.
1539- replace_names :list of strings or None
1539+ replace_names :List[str] or None
15401540 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.
15461543
15471544 Returns
15481545 -------
15491546 The augmented docstring.
15501547 """
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 )
15651556
15661557
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 ):
15691559"""
15701560 A decorator to add a 'data' kwarg to any a function. The signature
15711561 of the input function must include the ax argument at the first position ::
@@ -1576,216 +1566,109 @@ def foo(ax, *args, **kwargs)
15761566
15771567 Parameters
15781568 ----------
1579- replace_names :list of strings , optional, default: None
1569+ replace_names :List[str] or None , optional, default: None
15801570 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.
15861573 label_namer : string, optional, default: None
15871574 The name of the parameter which argument should be used as label, if
15881575 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!
16011576
16021577 .. note:: decorator also converts MappingView input data to list.
16031578 """
1604- if replace_names is not None :
1605- replace_names = set (replace_names )
16061579
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
16361595else :
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
17121604
1713- data = kwargs .pop ('data' ,None )
1605+ if replace_names is not None :
1606+ replace_names = set (replace_names )
17141607
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 )
17521620
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 :
17531636if 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
17741663else :
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
17821666
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 )
17871668
1788- return param
1669+ inner .__doc__ = _add_data_doc (inner .__doc__ ,replace_names )
1670+ inner .__signature__ = new_sig
1671+ return inner
17891672
17901673_log .debug ('matplotlib version %s' ,__version__ )
17911674_log .debug ('interactive is %s' ,is_interactive ())