Repository: matplotlib/matplotlib
File: lib/matplotlib/scale.py (Python)

Code before:
    Returns
    -------
    callable
        A wrapped version of *init_func* that handles the optional *axis*.

    Notes
    -----
    If the wrapped constructor defines *axis* as its first argument, the
    parameter is preserved. Otherwise, it is safely removed from positional
    or keyword arguments.

    Examples
    --------
    >>> from matplotlib.scale import ScaleBase
    >>> class CustomScale(ScaleBase):
    ...     @handle_axis_parameter
    ...     def __init__(self, axis=None, custom_param=1):
    ...         self.custom_param = custom_param
    """
    @wraps(init_func)
    def wrapper(self, *args, **kwargs):
        sig = inspect.signature(init_func)
        params = list(sig.parameters.values())
        if params and params[1].name == "axis":
            return init_func(self, *args, **kwargs)
        if args:
            args = args[1:]
        kwargs.pop("axis", None)
        return init_func(self, *args, **kwargs)
    return wrapper


class LinearScale(ScaleBase):
    """
    The default linear scale.
    """

    name = 'linear'

    @handle_axis_parameter
    def __init__(self, axis=None):
        # This method is present only to prevent inheritance of the base class'
        # constructor docstring, which would otherwise end up interpolated into
        # the docstring of Axis.set_scale.
        """
        """  # noqa: D419

    def set_default_locators_and_formatters(self, axis):
        # docstring inherited
        axis.set_major_locator(AutoLocator())
        axis.set_major_formatter(ScalarFormatter())

Review comment (suggestion):
AFAICS this is always true for all our decorated `__init__` functions, so the decorator effectively does nothing.

I think the decorator must be phrased slightly differently: For any `__init__(axis, ...)`, we currently have calls `TheScale(axis, ...)`. We now want to additionally support leaving out the *axis* parameter `TheScale(...)`. Derived scales may currently do `super().__init__(None, ...)`. The decorator will allow them to switch to `super().__init__(...)`.

For all scales that have only a single positional paramters, we can achieve this simply by making *axis* optional: `__init__(axis=None, ...)`. But this does not work for `FuncScale`, `FuncScaleLog` and `LogitScale`. So we need the decorator anyway and can apply it to all scales. This will also make it easier to later add deprecation messages.

For simplicity we can assume that the axis parameter is always passed positionally. Searching GitHub (e.g. [this search](https://github.com/search?q=%2Fclass+%5Cw%2B%5C%28%28.%5C.%29%3FLogScale%5C%29%2F+language%3Apython+path%3A*.py+NOT+is%3Afork+NOT+path%3A**%2Fmatplotlib%2Fscale.py+NOT+path%3A**%2Fsite-packages%2F**&type=code) - most of the expression is to remove code copies) did not yield any keyword usage of axis. I.e. basically we can reduce the wrapper to 

```
def wrapper(self, *args, **kwargs):
    if args and isinstance(args[0], ScaleBase):
         init_func(self, *args, **kwargs)
    else:
         # inject None as axis parameter
         init_func(self, None, *args, **kwargs)
 ```

We keep the parameter always around and used to still support custom derived classes. Only when they have migrated to not provide the parameter, we'll remove it from all our classes.


Code after:
    init_func : callable
        The original __init__ method of a scale class.

    Returns
    -------
    callable
        A wrapped version of *init_func* that handles the optional *axis*.

    Notes
    -----
    If the wrapped constructor defines *axis* as its first argument, the
    parameter is preserved when present. Otherwise, the value `None` is injected
    as the first argument.

    Examples
    --------
    >>> from matplotlib.scale import ScaleBase
    >>> class CustomScale(ScaleBase):
    ...     @_make_axis_parameter_optional
    ...     def __init__(self, axis, custom_param=1):
    ...         self.custom_param = custom_param
    """
    @wraps(init_func)
    def wrapper(self, *args, **kwargs):
        if args and isinstance(args[0], mpl.axis.Axis):
            return init_func(self, *args, **kwargs)
        else:
            # Remove 'axis' from kwargs to avoid double assignment
            axis = kwargs.pop('axis', None)
            return init_func(self, axis, *args, **kwargs)
    return wrapper


class LinearScale(ScaleBase):
    """
    The default linear scale.
    """

    name = 'linear'

    @_make_axis_parameter_optional
    def __init__(self, axis):
        # This method is present only to prevent inheritance of the base class'
        # constructor docstring, which would otherwise end up interpolated into
        # the docstring of Axis.set_scale.
        """
        """  # noqa: D419

    def set_default_locators_and_formatters(self, axis):
        # docstring inherited
        axis.set_major_locator(AutoLocator())