Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e09d088

Browse filesBrowse files
authored
Merge pull request #20686 from anntzer/mkopyplot
Fix interaction between make_keyword_only and pyplot generation.
2 parents 45528c3 + a8abb76 commit e09d088
Copy full SHA for e09d088

File tree

Expand file treeCollapse file tree

5 files changed

+44
-26
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+44
-26
lines changed
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
All parameters of ``imshow`` starting from *aspect* will become keyword-only
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

‎lib/matplotlib/_api/deprecation.py

Copy file name to clipboardExpand all lines: lib/matplotlib/_api/deprecation.py
+31-9Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,14 @@ def __set_name__(self, owner, name):
249249
name=name))
250250

251251

252+
# Used by _copy_docstring_and_deprecators to redecorate pyplot wrappers and
253+
# boilerplate.py to retrieve original signatures. It may seem natural to store
254+
# this information as an attribute on the wrapper, but if the wrapper gets
255+
# itself functools.wraps()ed, then such attributes are silently propagated to
256+
# the outer wrapper, which is not desired.
257+
DECORATORS = {}
258+
259+
252260
def rename_parameter(since, old, new, func=None):
253261
"""
254262
Decorator indicating that parameter *old* of *func* is renamed to *new*.
@@ -268,8 +276,10 @@ def rename_parameter(since, old, new, func=None):
268276
def func(good_name): ...
269277
"""
270278

279+
decorator = functools.partial(rename_parameter, since, old, new)
280+
271281
if func is None:
272-
return functools.partial(rename_parameter, since, old, new)
282+
return decorator
273283

274284
signature = inspect.signature(func)
275285
assert old not in signature.parameters, (
@@ -294,6 +304,7 @@ def wrapper(*args, **kwargs):
294304
# would both show up in the pyplot function for an Axes method as well and
295305
# pyplot would explicitly pass both arguments to the Axes method.
296306

307+
DECORATORS[wrapper] = decorator
297308
return wrapper
298309

299310

@@ -330,8 +341,10 @@ def delete_parameter(since, name, func=None, **kwargs):
330341
def func(used_arg, other_arg, unused, more_args): ...
331342
"""
332343

344+
decorator = functools.partial(delete_parameter, since, name, **kwargs)
345+
333346
if func is None:
334-
return functools.partial(delete_parameter, since, name, **kwargs)
347+
return decorator
335348

336349
signature = inspect.signature(func)
337350
# Name of `**kwargs` parameter of the decorated function, typically
@@ -399,17 +412,24 @@ def wrapper(*inner_args, **inner_kwargs):
399412
**kwargs)
400413
return func(*inner_args, **inner_kwargs)
401414

415+
DECORATORS[wrapper] = decorator
402416
return wrapper
403417

404418

405419
def make_keyword_only(since, name, func=None):
406420
"""
407421
Decorator indicating that passing parameter *name* (or any of the following
408422
ones) positionally to *func* is being deprecated.
423+
424+
When used on a method that has a pyplot wrapper, this should be the
425+
outermost decorator, so that :file:`boilerplate.py` can access the original
426+
signature.
409427
"""
410428

429+
decorator = functools.partial(make_keyword_only, since, name)
430+
411431
if func is None:
412-
return functools.partial(make_keyword_only, since, name)
432+
return decorator
413433

414434
signature = inspect.signature(func)
415435
POK = inspect.Parameter.POSITIONAL_OR_KEYWORD
@@ -419,26 +439,28 @@ def make_keyword_only(since, name, func=None):
419439
f"Matplotlib internal error: {name!r} must be a positional-or-keyword "
420440
f"parameter for {func.__name__}()")
421441
names = [*signature.parameters]
422-
kwonly = [name for name in names[names.index(name):]
442+
name_idx = names.index(name)
443+
kwonly = [name for name in names[name_idx:]
423444
if signature.parameters[name].kind == POK]
424-
func.__signature__ = signature.replace(parameters=[
425-
param.replace(kind=KWO) if param.name in kwonly else param
426-
for param in signature.parameters.values()])
427445

428446
@functools.wraps(func)
429447
def wrapper(*args, **kwargs):
430448
# Don't use signature.bind here, as it would fail when stacked with
431449
# rename_parameter and an "old" argument name is passed in
432450
# (signature.bind would fail, but the actual call would succeed).
433-
idx = [*func.__signature__.parameters].index(name)
434-
if len(args) > idx:
451+
if len(args) > name_idx:
435452
warn_deprecated(
436453
since, message="Passing the %(name)s %(obj_type)s "
437454
"positionally is deprecated since Matplotlib %(since)s; the "
438455
"parameter will become keyword-only %(removal)s.",
439456
name=name, obj_type=f"parameter of {func.__name__}()")
440457
return func(*args, **kwargs)
441458

459+
# Don't modify *func*'s signature, as boilerplate.py needs it.
460+
wrapper.__signature__ = signature.replace(parameters=[
461+
param.replace(kind=KWO) if param.name in kwonly else param
462+
for param in signature.parameters.values()])
463+
DECORATORS[wrapper] = decorator
442464
return wrapper
443465

444466

‎lib/matplotlib/axes/_axes.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_axes.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5285,6 +5285,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None,
52855285
replace_names=["y", "x1", "x2", "where"])
52865286

52875287
#### plotting z(x, y): imshow, pcolor and relatives, contour
5288+
@_api.make_keyword_only("3.5", "aspect")
52885289
@_preprocess_data()
52895290
def imshow(self, X, cmap=None, norm=None, aspect=None,
52905291
interpolation=None, alpha=None, vmin=None, vmax=None,

‎lib/matplotlib/pyplot.py

Copy file name to clipboardExpand all lines: lib/matplotlib/pyplot.py
+3-16Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,6 @@
7272
_log = logging.getLogger(__name__)
7373

7474

75-
_code_objs = {
76-
_api.rename_parameter:
77-
_api.rename_parameter("", "old", "new", lambda new: None).__code__,
78-
_api.make_keyword_only:
79-
_api.make_keyword_only("", "p", lambda p: None).__code__,
80-
}
81-
82-
8375
def _copy_docstring_and_deprecators(method, func=None):
8476
if func is None:
8577
return functools.partial(_copy_docstring_and_deprecators, method)
@@ -88,14 +80,9 @@ def _copy_docstring_and_deprecators(method, func=None):
8880
# or @_api.make_keyword_only decorators; if so, propagate them to the
8981
# pyplot wrapper as well.
9082
while getattr(method, "__wrapped__", None) is not None:
91-
for decorator_maker, code in _code_objs.items():
92-
if method.__code__ is code:
93-
kwargs = {
94-
k: v.cell_contents
95-
for k, v in zip(code.co_freevars, method.__closure__)}
96-
assert kwargs["func"] is method.__wrapped__
97-
kwargs.pop("func")
98-
decorators.append(decorator_maker(**kwargs))
83+
decorator = _api.deprecation.DECORATORS.get(method)
84+
if decorator:
85+
decorators.append(decorator)
9986
method = method.__wrapped__
10087
for decorator in decorators[::-1]:
10188
func = decorator(func)

‎tools/boilerplate.py

Copy file name to clipboardExpand all lines: tools/boilerplate.py
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,13 @@ def generate_function(name, called_fullname, template, **kwargs):
138138
class_name, called_name = called_fullname.split('.')
139139
class_ = {'Axes': Axes, 'Figure': Figure}[class_name]
140140

141-
signature = inspect.signature(getattr(class_, called_name))
141+
meth = getattr(class_, called_name)
142+
decorator = _api.deprecation.DECORATORS.get(meth)
143+
# Generate the wrapper with the non-kwonly signature, as it will get
144+
# redecorated with make_keyword_only by _copy_docstring_and_deprecators.
145+
if decorator and decorator.func is _api.make_keyword_only:
146+
meth = meth.__wrapped__
147+
signature = inspect.signature(meth)
142148
# Replace self argument.
143149
params = list(signature.parameters.values())[1:]
144150
signature = str(signature.replace(parameters=[

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.