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

Skip to content

Refactoring: Removing axis parameter from scales #29988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 65 additions & 6 deletions lib/matplotlib/scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import inspect
import textwrap
from functools import wraps

import numpy as np

Expand Down Expand Up @@ -103,14 +104,61 @@
return vmin, vmax


def _make_axis_parameter_optional(init_func):
"""
Decorator to allow leaving out the *axis* parameter in scale constructors.

This decorator ensures backward compatibility for scale classes that
previously required an *axis* parameter. It allows constructors to be
callerd with or without the *axis* parameter.

Check failure on line 114 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Blank line contains whitespace Raw Output: message:"Blank line contains whitespace" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:114 column:1} end:{line:114 column:5}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"W293" url:"https://docs.astral.sh/ruff/rules/blank-line-with-whitespace"} suggestions:{range:{start:{line:114 column:1} end:{line:114 column:5}}}
For simplicity, this does not handle the case when *axis* is passed as a keyword. Howver,

Check failure on line 115 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Line too long (93 > 88) Raw Output: message:"Line too long (93 > 88)" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:115 column:89} end:{line:115 column:94}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"E501" url:"https://docs.astral.sh/ruff/rules/line-too-long"}
scanning GitHub, there's no evidence that that is used anywhere.

Parameters
----------
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):
... @handle_axis_parameter
... def __init__(self, axis, custom_param=1):
... self.custom_param = custom_param
"""
@wraps(init_func)
def wrapper(self, *args, **kwargs):

Check warning on line 143 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L142-L143

Added lines #L142 - L143 were not covered by tests
if args and isinstance(args[0], mpl.axis.Axis):
return init_func(self, *args, **kwargs)

Check warning on line 145 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L145

Added line #L145 was not covered by tests
else:
# Remove 'axis' from kwargs to avoid double assignment
kwargs.pop('axis', None)
return init_func(self, None, *args, **kwargs)
return wrapper

Check warning on line 150 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L148-L150

Added lines #L148 - L150 were not covered by tests


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

name = 'linear'

def __init__(self, axis):
@handle_axis_parameter

Check failure on line 160 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `handle_axis_parameter` Raw Output: message:"Undefined name `handle_axis_parameter`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:160 column:6} end:{line:160 column:27}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}
def __init__(self, axis=None):

Check warning on line 161 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L161

Added line #L161 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an advantage in making axis optional here? That should be handled be handled by the decorator. If there's no particular reason, then I'd prefer to keep the signature formally unchanged for now.

# 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.
Expand Down Expand Up @@ -180,6 +228,7 @@

name = 'function'

@handle_axis_parameter

Check failure on line 231 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `handle_axis_parameter` Raw Output: message:"Undefined name `handle_axis_parameter`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:231 column:6} end:{line:231 column:27}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}

Check warning on line 231 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L231

Added line #L231 was not covered by tests
def __init__(self, axis, functions):
"""
Parameters
Expand Down Expand Up @@ -279,7 +328,8 @@
"""
name = 'log'

def __init__(self, axis, *, base=10, subs=None, nonpositive="clip"):
@handle_axis_parameter

Check failure on line 331 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `handle_axis_parameter` Raw Output: message:"Undefined name `handle_axis_parameter`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:331 column:6} end:{line:331 column:27}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}
def __init__(self, axis=None, *, base=10, subs=None, nonpositive="clip"):

Check warning on line 332 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L331-L332

Added lines #L331 - L332 were not covered by tests
"""
Parameters
----------
Expand Down Expand Up @@ -330,6 +380,7 @@

name = 'functionlog'

@handle_axis_parameter

Check failure on line 383 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `handle_axis_parameter` Raw Output: message:"Undefined name `handle_axis_parameter`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:383 column:6} end:{line:383 column:27}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}

Check warning on line 383 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L383

Added line #L383 was not covered by tests
def __init__(self, axis, functions, base=10):
"""
Parameters
Expand Down Expand Up @@ -455,7 +506,8 @@
"""
name = 'symlog'

def __init__(self, axis, *, base=10, linthresh=2, subs=None, linscale=1):
@handle_axis_parameter

Check failure on line 509 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `handle_axis_parameter` Raw Output: message:"Undefined name `handle_axis_parameter`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:509 column:6} end:{line:509 column:27}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}
def __init__(self, axis=None, *, base=10, linthresh=2, subs=None, linscale=1):

Check warning on line 510 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L509-L510

Added lines #L509 - L510 were not covered by tests
self._transform = SymmetricalLogTransform(base, linthresh, linscale)
self.subs = subs

Expand Down Expand Up @@ -547,7 +599,8 @@
1024: (256, 512)
}

def __init__(self, axis, *, linear_width=1.0,
@handle_axis_parameter

Check failure on line 602 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `handle_axis_parameter` Raw Output: message:"Undefined name `handle_axis_parameter`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:602 column:6} end:{line:602 column:27}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}
def __init__(self, axis=None, *, linear_width=1.0,

Check warning on line 603 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L602-L603

Added lines #L602 - L603 were not covered by tests
base=10, subs='auto', **kwargs):
"""
Parameters
Expand Down Expand Up @@ -645,7 +698,8 @@
"""
name = 'logit'

def __init__(self, axis, nonpositive='mask', *,
@handle_axis_parameter

Check failure on line 701 in lib/matplotlib/scale.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Undefined name `handle_axis_parameter` Raw Output: message:"Undefined name `handle_axis_parameter`" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/scale.py" range:{start:{line:701 column:6} end:{line:701 column:27}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"F821" url:"https://docs.astral.sh/ruff/rules/undefined-name"}
def __init__(self, axis=None, nonpositive='mask', *,

Check warning on line 702 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L701-L702

Added lines #L701 - L702 were not covered by tests
one_half=r"\frac{1}{2}", use_overline=False):
r"""
Parameters
Expand Down Expand Up @@ -725,7 +779,12 @@
axis : `~matplotlib.axis.Axis`
"""
scale_cls = _api.check_getitem(_scale_mapping, scale=scale)
return scale_cls(axis, **kwargs)
try:
return scale_cls(axis, **kwargs)
except TypeError as e:

Check warning on line 784 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L782-L784

Added lines #L782 - L784 were not covered by tests
if 'unexpected keyword argument' in str(e) or 'positional argument' in str(e):
return scale_cls(**kwargs)
raise

Check warning on line 787 in lib/matplotlib/scale.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/scale.py#L786-L787

Added lines #L786 - L787 were not covered by tests
Comment on lines +784 to +787
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is safe? Basing logic on the exception message only can be brittle. Minimally, I'd want an explicit comment on what we catch here. But I'd rather split this out into a separte PR as we need tests.



if scale_factory.__doc__:
Expand Down
38 changes: 20 additions & 18 deletions lib/matplotlib/scale.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ from matplotlib.axis import Axis
from matplotlib.transforms import Transform

from collections.abc import Callable, Iterable
from typing import Literal
from typing import Literal, Union
from numpy.typing import ArrayLike

class ScaleBase:
Expand All @@ -15,6 +15,7 @@ class ScaleBase:

class LinearScale(ScaleBase):
name: str
def __init__(self: ScaleBase, axis: Union[Axis, None] = None) -> None: ...

class FuncTransform(Transform):
input_dims: int
Expand Down Expand Up @@ -56,12 +57,12 @@ class LogScale(ScaleBase):
name: str
subs: Iterable[int] | None
def __init__(
self,
axis: Axis | None,
self: LogScale,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't type self. It's implicitly understood by typecheckers.

axis: Axis | None = ...,
*,
base: float = ...,
subs: Iterable[int] | None = ...,
nonpositive: Literal["clip", "mask"] = ...
base: float = 10,
subs: Union[Iterable[int], None] = None,
nonpositive: Union[Literal['clip'], Literal['mask']] = 'clip'
) -> None: ...
@property
def base(self) -> float: ...
Expand Down Expand Up @@ -103,13 +104,13 @@ class SymmetricalLogScale(ScaleBase):
name: str
subs: Iterable[int] | None
def __init__(
self,
axis: Axis | None,
self: SymmetricalLogScale,
axis: Union[Axis, None] = None,
*,
base: float = ...,
linthresh: float = ...,
subs: Iterable[int] | None = ...,
linscale: float = ...
base: float = 10,
linthresh: float = 2,
subs: Union[Iterable[int], None] = None,
linscale: float = 1
) -> None: ...
@property
def base(self) -> float: ...
Expand Down Expand Up @@ -138,7 +139,7 @@ class AsinhScale(ScaleBase):
auto_tick_multipliers: dict[int, tuple[int, ...]]
def __init__(
self,
axis: Axis | None,
axis: Union[Axis, None] = None,
*,
linear_width: float = ...,
base: float = ...,
Expand All @@ -164,15 +165,16 @@ class LogisticTransform(Transform):
class LogitScale(ScaleBase):
name: str
def __init__(
self,
axis: Axis | None,
nonpositive: Literal["mask", "clip"] = ...,
self: LogitScale,
axis: Union[Axis, None] = None,
nonpositive: Union[Literal['mask'], Literal['clip']] = 'mask',
*,
one_half: str = ...,
use_overline: bool = ...
one_half: str = '\\frac{1}{2}',
use_overline: bool = False
) -> None: ...
def get_transform(self) -> LogitTransform: ...

def get_scale_names() -> list[str]: ...
def scale_factory(scale: str, axis: Axis, **kwargs) -> ScaleBase: ...
def register_scale(scale_class: type[ScaleBase]) -> None: ...
def handle_axis_parameter(init_func: Callable[..., None]) -> Callable[..., None]: ...
Loading