|
1 | 1 | from collections import OrderedDict
|
2 | 2 | from contextlib import ExitStack
|
3 |
| -import functools |
4 | 3 | import inspect
|
5 | 4 | import itertools
|
6 | 5 | import logging
|
|
31 | 30 | _log = logging.getLogger(__name__)
|
32 | 31 |
|
33 | 32 |
|
34 |
| -def _axis_method_wrapper(attr_name, method_name, *, doc_sub=None): |
| 33 | +class _axis_method_wrapper: |
35 | 34 | """
|
36 | 35 | Helper to generate Axes methods wrapping Axis methods.
|
37 | 36 |
|
38 | 37 | After ::
|
39 | 38 |
|
40 | 39 | get_foo = _axis_method_wrapper("xaxis", "get_bar")
|
41 | 40 |
|
42 |
| - ``get_foo`` is a method that forwards it arguments to the ``get_bar`` |
43 |
| - method of the ``xaxis`` attribute, and gets its signature and docstring |
44 |
| - from ``Axis.get_bar``. |
| 41 | + (in the body of a class) ``get_foo`` is a method that forwards it arguments |
| 42 | + to the ``get_bar`` method of the ``xaxis`` attribute, and gets its |
| 43 | + signature and docstring from ``Axis.get_bar``. |
45 | 44 |
|
46 | 45 | The docstring of ``get_foo`` is built by replacing "this Axis" by "the
|
47 |
| - {attr_name}" ("the xaxis", "the yaxis") in the wrapped method's docstring; |
48 |
| - additional replacements can by given in *doc_sub*. The docstring is also |
49 |
| - dedented to simplify further manipulations. |
| 46 | + {attr_name}" (i.e., "the xaxis", "the yaxis") in the wrapped method's |
| 47 | + docstring; additional replacements can by given in *doc_sub*. The |
| 48 | + docstring is also dedented to simplify further manipulations. |
50 | 49 | """
|
51 | 50 |
|
52 |
| - method = getattr(maxis.Axis, method_name) |
53 |
| - get_method = attrgetter(f"{attr_name}.{method_name}") |
54 |
| - |
55 |
| - @functools.wraps(method) |
56 |
| - def wrapper(self, *args, **kwargs): |
57 |
| - return get_method(self)(*args, **kwargs) |
58 |
| - |
59 |
| - doc = wrapper.__doc__ |
60 |
| - if doc: |
61 |
| - doc_sub = {"this Axis": f"the {attr_name}", **(doc_sub or {})} |
62 |
| - for k, v in doc_sub.items(): |
63 |
| - assert k in doc, \ |
64 |
| - (f"The docstring of wrapped Axis method {method_name!r} must " |
65 |
| - f"contain {k!r} as a substring.") |
66 |
| - doc = doc.replace(k, v) |
67 |
| - wrapper.__doc__ = inspect.cleandoc(doc) |
68 |
| - |
69 |
| - return wrapper |
| 51 | + def __init__(self, attr_name, method_name, *, doc_sub=None): |
| 52 | + self.attr_name = attr_name |
| 53 | + self.method_name = method_name |
| 54 | + self.doc_sub = doc_sub |
| 55 | + |
| 56 | + def __set_name__(self, owner, name): |
| 57 | + # This is called at the end of the class body as |
| 58 | + # ``self.__set_name__(cls, name_under_which_self_is_assigned)``; we |
| 59 | + # rely on that to give the wrapper the correct __name__/__qualname__. |
| 60 | + get_method = attrgetter(f"{self.attr_name}.{self.method_name}") |
| 61 | + |
| 62 | + def wrapper(self, *args, **kwargs): |
| 63 | + return get_method(self)(*args, **kwargs) |
| 64 | + |
| 65 | + wrapper.__module__ = owner.__module__ |
| 66 | + wrapper.__name__ = name |
| 67 | + wrapper.__qualname__ = f"{owner.__qualname__}.{name}" |
| 68 | + # Manually copy the signature instead of using functools.wraps because |
| 69 | + # displaying the Axis method source when asking for the Axes method |
| 70 | + # source would be confusing. |
| 71 | + wrapped_method = getattr(maxis.Axis, self.method_name) |
| 72 | + wrapper.__signature__ = inspect.signature(wrapped_method) |
| 73 | + doc = wrapped_method.__doc__ |
| 74 | + if doc: |
| 75 | + doc_sub = {"this Axis": f"the {self.attr_name}", |
| 76 | + **(self.doc_sub or {})} |
| 77 | + for k, v in doc_sub.items(): |
| 78 | + assert k in doc, \ |
| 79 | + (f"The definition of {wrapper.__qualname__} expected that " |
| 80 | + f"the docstring of Axis.{self.method_name} contains " |
| 81 | + f"{k!r} as a substring.") |
| 82 | + doc = doc.replace(k, v) |
| 83 | + wrapper.__doc__ = inspect.cleandoc(doc) |
| 84 | + |
| 85 | + setattr(owner, name, wrapper) |
70 | 86 |
|
71 | 87 |
|
72 | 88 | def _process_plot_format(fmt):
|
|
0 commit comments