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

Skip to content

Commit e09d088

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

File tree

5 files changed

+44
-26
lines changed

5 files changed

+44
-26
lines changed
Lines 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

Lines 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

Lines 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

Lines 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

Lines 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)