Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5dee947

Browse files
authored
Merge pull request #31023 from anntzer/nk
Speedup normalize_kwargs by storing aliases in a more practical format.
2 parents a895fb3 + d4ce763 commit 5dee947

6 files changed

Lines changed: 68 additions & 43 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``cbook.normalize_kwargs`` only supports passing artists and artist classes as second argument
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Support for directly passing an alias mapping or None as second argument to
4+
`.cbook.normalize_kwargs` has been deprecated.

lib/matplotlib/_api/__init__.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,9 @@ class so far, an alias named ``get_alias`` will be defined; the same will
269269
be done for setters. If neither the getter nor the setter exists, an
270270
exception will be raised.
271271
272-
The alias map is stored as the ``_alias_map`` attribute on the class and
273-
can be used by `.normalize_kwargs`.
272+
The alias map is stored as the ``_alias_to_prop`` attribute under the format
273+
``{"alias": "property", ...}` on the class, and can be used by
274+
`.normalize_kwargs`.
274275
"""
275276
if cls is None: # Return the actual class decorator.
276277
return functools.partial(define_aliases, alias_d)
@@ -295,17 +296,20 @@ def method(self, *args, **kwargs):
295296
raise ValueError(
296297
f"Neither getter nor setter exists for {prop!r}")
297298

299+
alias_to_prop = {
300+
alias: prop for prop, aliases in alias_d.items() for alias in aliases}
301+
298302
def get_aliased_and_aliases(d):
299-
return {*d, *(alias for aliases in d.values() for alias in aliases)}
303+
return {*d.keys(), *d.values()}
300304

301-
preexisting_aliases = getattr(cls, "_alias_map", {})
305+
preexisting_aliases = getattr(cls, "_alias_to_prop", {})
302306
conflicting = (get_aliased_and_aliases(preexisting_aliases)
303-
& get_aliased_and_aliases(alias_d))
307+
& get_aliased_and_aliases(alias_to_prop))
304308
if conflicting:
305309
# Need to decide on conflict resolution policy.
306310
raise NotImplementedError(
307311
f"Parent class already defines conflicting aliases: {conflicting}")
308-
cls._alias_map = {**preexisting_aliases, **alias_d}
312+
cls._alias_to_prop = {**preexisting_aliases, **alias_to_prop}
309313
return cls
310314

311315

lib/matplotlib/cbook.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,7 +1831,7 @@ def normalize_kwargs(kw, alias_mapping=None):
18311831
as an empty dict, to support functions with an optional parameter of
18321832
the form ``props=None``.
18331833
1834-
alias_mapping : dict or Artist subclass or Artist instance, optional
1834+
alias_mapping : Artist subclass or Artist instance
18351835
A mapping between a canonical name to a list of aliases, in order of
18361836
precedence from lowest to highest.
18371837
@@ -1849,31 +1849,35 @@ def normalize_kwargs(kw, alias_mapping=None):
18491849
"""
18501850
from matplotlib.artist import Artist
18511851

1852+
# deal with default value of alias_mapping
1853+
if (isinstance(alias_mapping, type) and issubclass(alias_mapping, Artist)
1854+
or isinstance(alias_mapping, Artist)):
1855+
alias_to_prop = getattr(alias_mapping, "_alias_to_prop", {})
1856+
else:
1857+
if alias_mapping is None:
1858+
alias_mapping = {}
1859+
_api.warn_deprecated("3.11", message=(
1860+
"Passing a dict or None as alias_mapping to normalize_kwargs is "
1861+
"deprecated since %(since)s and support will be removed "
1862+
"%(removal)s; pass an Artist instance or type instead."))
1863+
# Convert old format to new format.
1864+
alias_to_prop = {alias: prop for prop, aliases in alias_mapping.items()
1865+
for alias in aliases}
1866+
18521867
if kw is None:
18531868
return {}
18541869

1855-
# deal with default value of alias_mapping
1856-
if alias_mapping is None:
1857-
alias_mapping = {}
1858-
elif (isinstance(alias_mapping, type) and issubclass(alias_mapping, Artist)
1859-
or isinstance(alias_mapping, Artist)):
1860-
alias_mapping = getattr(alias_mapping, "_alias_map", {})
1870+
canonicalized = {alias_to_prop.get(k, k): v for k, v in kw.items()}
1871+
if len(canonicalized) == len(kw):
1872+
return canonicalized
18611873

1862-
to_canonical = {alias: canonical
1863-
for canonical, alias_list in alias_mapping.items()
1864-
for alias in alias_list}
18651874
canonical_to_seen = {}
1866-
ret = {} # output dictionary
1867-
1868-
for k, v in kw.items():
1869-
canonical = to_canonical.get(k, k)
1875+
for k in kw:
1876+
canonical = alias_to_prop.get(k, k)
18701877
if canonical in canonical_to_seen:
18711878
raise TypeError(f"Got both {canonical_to_seen[canonical]!r} and "
18721879
f"{k!r}, which are aliases of one another")
18731880
canonical_to_seen[canonical] = k
1874-
ret[canonical] = v
1875-
1876-
return ret
18771881

18781882

18791883
@contextlib.contextmanager

lib/matplotlib/cbook.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def sanitize_sequence(data): ...
150150
def _resize_sequence(seq: Sequence, N: int) -> Sequence: ...
151151
def normalize_kwargs(
152152
kw: dict[str, Any],
153-
alias_mapping: dict[str, list[str]] | type[Artist] | Artist | None = ...,
153+
alias_mapping: type[Artist] | Artist | None = ...,
154154
) -> dict[str, Any]: ...
155155
def _lock_path(path: str | os.PathLike) -> contextlib.AbstractContextManager[None]: ...
156156
def _str_equal(obj: Any, s: str) -> bool: ...

lib/matplotlib/tests/test_cbook.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
assert_array_almost_equal)
1616
import pytest
1717

18+
import matplotlib as mpl
1819
from matplotlib import _api, cbook
1920
import matplotlib.colors as mcolors
2021
from matplotlib.cbook import delete_masked_points, strip_math
@@ -480,30 +481,42 @@ def test_resize_sequence():
480481
assert_array_equal(cbook._resize_sequence(arr, 5), [1, 2, 3, 1, 2])
481482

482483

483-
fail_mapping: tuple[tuple[dict, dict], ...] = (
484-
({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['b']}}),
485-
({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
486-
)
484+
fail_mapping: list[tuple[dict, dict]] = [
485+
({"a": 1, "b": 2}, {"a": ["b"]}),
486+
({"a": 1, "b": 2}, {"a": ["a", "b"]}),
487+
]
487488

488-
pass_mapping: tuple[tuple[Any, dict, dict], ...] = (
489+
pass_mapping: list[tuple[Any, dict, dict]] = [
489490
(None, {}, {}),
490-
({'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {}),
491-
({'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
492-
)
491+
({"a": 1, "b": 2}, {"a": 1, "b": 2}, {}),
492+
({"b": 2}, {"a": 2}, {"a": ["a", "b"]}),
493+
]
493494

494495

495-
@pytest.mark.parametrize('inp, kwargs_to_norm', fail_mapping)
496-
def test_normalize_kwargs_fail(inp, kwargs_to_norm):
497-
with pytest.raises(TypeError), _api.suppress_matplotlib_deprecation_warning():
498-
cbook.normalize_kwargs(inp, **kwargs_to_norm)
496+
@pytest.mark.parametrize('inp, alias_def', fail_mapping)
497+
def test_normalize_kwargs_fail(inp, alias_def):
499498

499+
@_api.define_aliases(alias_def)
500+
class Type(mpl.artist.Artist):
501+
def get_a(self): return None
500502

501-
@pytest.mark.parametrize('inp, expected, kwargs_to_norm',
502-
pass_mapping)
503-
def test_normalize_kwargs_pass(inp, expected, kwargs_to_norm):
504-
with _api.suppress_matplotlib_deprecation_warning():
505-
# No other warning should be emitted.
506-
assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm)
503+
with pytest.raises(TypeError):
504+
cbook.normalize_kwargs(inp, Type)
505+
506+
507+
@pytest.mark.parametrize('inp, expected, alias_def', pass_mapping)
508+
def test_normalize_kwargs_pass(inp, expected, alias_def):
509+
510+
@_api.define_aliases(alias_def)
511+
class Type(mpl.artist.Artist):
512+
def get_a(self): return None
513+
514+
assert expected == cbook.normalize_kwargs(inp, Type)
515+
old_alias_map = {}
516+
for alias, prop in Type._alias_to_prop.items():
517+
old_alias_map.setdefault(prop, []).append(alias)
518+
with pytest.warns(mpl.MatplotlibDeprecationWarning):
519+
assert expected == cbook.normalize_kwargs(inp, old_alias_map)
507520

508521

509522
def test_warn_external(recwarn):

lib/matplotlib/widgets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3089,7 +3089,7 @@ def __init__(self, ax, x, y, *, marker='o', marker_props=None, useblit=True):
30893089
props = {'marker': marker, 'markersize': 7, 'markerfacecolor': 'w',
30903090
'linestyle': 'none', 'alpha': 0.5, 'visible': False,
30913091
'label': '_nolegend_',
3092-
**cbook.normalize_kwargs(marker_props, Line2D._alias_map)}
3092+
**cbook.normalize_kwargs(marker_props, Line2D)}
30933093
self._markers = Line2D(x, y, animated=useblit, **props)
30943094
self.ax.add_line(self._markers)
30953095

0 commit comments

Comments
 (0)