diff options
| author | Bas van Beek <43369155+BvB93@users.noreply.github.com> | 2021-07-20 15:41:15 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-07-20 08:41:15 -0500 |
| commit | f4353323d0be0c3b3c7d0c752d6c135bc6d36465 (patch) | |
| tree | 1466100a94854e4fd1c3df565b37cf09240effe4 | |
| parent | 3cec3ae80e626952a6b61a72516713217d99fefe (diff) | |
| download | numpy-f4353323d0be0c3b3c7d0c752d6c135bc6d36465.tar.gz | |
ENH: Add the `axis` and `ndim` attributes to `np.AxisError` (#19459)
This PR adds the new axis and ndim attributes to the np.AxisError class, an addition inspired by similar
changes introduced to AttributeError in Python 3.10.
It also provided an excuse to update the classes' documentation and tests, both of which were previously rather lacking.
* ENH: Add the `axis` and `ndim` attributes to `np.AxisError`
* MAINT: Let the new `AxisError` attributes survive pickling
* DOC: Update the `AxisError` documentation
* TST: Update the `AxisError` tests
* DOC: Add a release note
* MAINT: Improve the description of `AxisError`s overloaded constructor
* TST: Fix a few typing test failures
* DOC: Clarify a fix a few parts of the `AxisError` docstrings
Co-Authored-By: Eric Wieser <425260+eric-wieser@users.noreply.github.com>
* DOC: Reguide fix
* DOC: Replace `versionadded` with `versionchanged`
Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
* DOC: Mention that `AxisError` is a `ValueError` and `IndexError` subclass
Co-Authored-By: Sebastian Berg <sebastian@sipsolutions.net>
* ENH: Delay assembling of the error message until `__str__` is called
Co-Authored-By: Eric Wieser <425260+eric-wieser@users.noreply.github.com>
* DOC: Update the `AxisError` string representation in its docstring
* DOC: Update `versionadded`-related information
Co-Authored-By: Eric Wieser <425260+eric-wieser@users.noreply.github.com>
* DOC: Fix sphinx warnings
Co-authored-by: Eric Wieser <425260+eric-wieser@users.noreply.github.com>
Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
| -rw-r--r-- | doc/release/upcoming_changes/19459.new_feature.rst | 4 | ||||
| -rw-r--r-- | doc/source/reference/routines.other.rst | 9 | ||||
| -rw-r--r-- | numpy/__init__.pyi | 9 | ||||
| -rw-r--r-- | numpy/core/_exceptions.py | 96 | ||||
| -rw-r--r-- | numpy/core/tests/test__exceptions.py | 30 | ||||
| -rw-r--r-- | numpy/typing/tests/data/fail/warnings_and_errors.py | 8 | ||||
| -rw-r--r-- | numpy/typing/tests/data/pass/warnings_and_errors.py | 3 | ||||
| -rw-r--r-- | numpy/typing/tests/data/reveal/warnings_and_errors.py | 3 |
8 files changed, 139 insertions, 23 deletions
diff --git a/doc/release/upcoming_changes/19459.new_feature.rst b/doc/release/upcoming_changes/19459.new_feature.rst new file mode 100644 index 000000000..aecae670f --- /dev/null +++ b/doc/release/upcoming_changes/19459.new_feature.rst @@ -0,0 +1,4 @@ +The ``ndim`` and ``axis`` attributes have been added to `numpy.AxisError` +------------------------------------------------------------------------- +The ``ndim`` and ``axis`` parameters are now also stored as attributes +within each `numpy.AxisError` instance. diff --git a/doc/source/reference/routines.other.rst b/doc/source/reference/routines.other.rst index aefd680bb..339857409 100644 --- a/doc/source/reference/routines.other.rst +++ b/doc/source/reference/routines.other.rst @@ -55,4 +55,11 @@ Matlab-like Functions :toctree: generated/ who - disp
\ No newline at end of file + disp + +Exceptions +---------- +.. autosummary:: + :toctree: generated/ + + AxisError diff --git a/numpy/__init__.pyi b/numpy/__init__.pyi index cafa296eb..413a32569 100644 --- a/numpy/__init__.pyi +++ b/numpy/__init__.pyi @@ -3681,9 +3681,12 @@ class RankWarning(UserWarning): ... class TooHardError(RuntimeError): ... class AxisError(ValueError, IndexError): - def __init__( - self, axis: int, ndim: Optional[int] = ..., msg_prefix: Optional[str] = ... - ) -> None: ... + axis: None | int + ndim: None | int + @overload + def __init__(self, axis: str, ndim: None = ..., msg_prefix: None = ...) -> None: ... + @overload + def __init__(self, axis: int, ndim: int, msg_prefix: None | str = ...) -> None: ... _CallType = TypeVar("_CallType", bound=Union[_ErrFunc, _SupportsWrite]) diff --git a/numpy/core/_exceptions.py b/numpy/core/_exceptions.py index 77aa2f6e1..3cd8042ce 100644 --- a/numpy/core/_exceptions.py +++ b/numpy/core/_exceptions.py @@ -122,20 +122,94 @@ class TooHardError(RuntimeError): @set_module('numpy') class AxisError(ValueError, IndexError): - """ Axis supplied was invalid. """ - def __init__(self, axis, ndim=None, msg_prefix=None): - # single-argument form just delegates to base class - if ndim is None and msg_prefix is None: - msg = axis + """Axis supplied was invalid. + + This is raised whenever an ``axis`` parameter is specified that is larger + than the number of array dimensions. + For compatibility with code written against older numpy versions, which + raised a mixture of `ValueError` and `IndexError` for this situation, this + exception subclasses both to ensure that ``except ValueError`` and + ``except IndexError`` statements continue to catch `AxisError`. + + .. versionadded:: 1.13 + + Parameters + ---------- + axis : int or str + The out of bounds axis or a custom exception message. + If an axis is provided, then `ndim` should be specified as well. + ndim : int, optional + The number of array dimensions. + msg_prefix : str, optional + A prefix for the exception message. + + Attributes + ---------- + axis : int, optional + The out of bounds axis or ``None`` if a custom exception + message was provided. This should be the axis as passed by + the user, before any normalization to resolve negative indices. + + .. versionadded:: 1.22 + ndim : int, optional + The number of array dimensions or ``None`` if a custom exception + message was provided. + + .. versionadded:: 1.22 + + + Examples + -------- + >>> array_1d = np.arange(10) + >>> np.cumsum(array_1d, axis=1) + Traceback (most recent call last): + ... + numpy.AxisError: axis 1 is out of bounds for array of dimension 1 + + Negative axes are preserved: + + >>> np.cumsum(array_1d, axis=-2) + Traceback (most recent call last): + ... + numpy.AxisError: axis -2 is out of bounds for array of dimension 1 - # do the string formatting here, to save work in the C code + The class constructor generally takes the axis and arrays' + dimensionality as arguments: + + >>> print(np.AxisError(2, 1, msg_prefix='error')) + error: axis 2 is out of bounds for array of dimension 1 + + Alternatively, a custom exception message can be passed: + + >>> print(np.AxisError('Custom error message')) + Custom error message + + """ + + __slots__ = ("axis", "ndim", "_msg") + + def __init__(self, axis, ndim=None, msg_prefix=None): + if ndim is msg_prefix is None: + # single-argument form: directly set the error message + self._msg = axis + self.axis = None + self.ndim = None else: - msg = ("axis {} is out of bounds for array of dimension {}" - .format(axis, ndim)) - if msg_prefix is not None: - msg = "{}: {}".format(msg_prefix, msg) + self._msg = msg_prefix + self.axis = axis + self.ndim = ndim - super().__init__(msg) + def __str__(self): + axis = self.axis + ndim = self.ndim + + if axis is ndim is None: + return self._msg + else: + msg = f"axis {axis} is out of bounds for array of dimension {ndim}" + if self._msg is not None: + msg = f"{self._msg}: {msg}" + return msg @_display_as_base diff --git a/numpy/core/tests/test__exceptions.py b/numpy/core/tests/test__exceptions.py index 51c056936..c87412aa4 100644 --- a/numpy/core/tests/test__exceptions.py +++ b/numpy/core/tests/test__exceptions.py @@ -4,6 +4,7 @@ Tests of the ._exceptions module. Primarily for exercising the __str__ methods. import pickle +import pytest import numpy as np _ArrayMemoryError = np.core._exceptions._ArrayMemoryError @@ -56,3 +57,32 @@ class TestUFuncNoLoopError: def test_pickling(self): """ Test that _UFuncNoLoopError can be pickled """ assert isinstance(pickle.dumps(_UFuncNoLoopError), bytes) + + +@pytest.mark.parametrize("args", [ + (2, 1, None), + (2, 1, "test_prefix"), + ("test message",), +]) +class TestAxisError: + def test_attr(self, args): + """Validate attribute types.""" + exc = np.AxisError(*args) + if len(args) == 1: + assert exc.axis is None + assert exc.ndim is None + else: + axis, ndim, *_ = args + assert exc.axis == axis + assert exc.ndim == ndim + + def test_pickling(self, args): + """Test that `AxisError` can be pickled.""" + exc = np.AxisError(*args) + exc2 = pickle.loads(pickle.dumps(exc)) + + assert type(exc) is type(exc2) + for name in ("axis", "ndim", "args"): + attr1 = getattr(exc, name) + attr2 = getattr(exc2, name) + assert attr1 == attr2, name diff --git a/numpy/typing/tests/data/fail/warnings_and_errors.py b/numpy/typing/tests/data/fail/warnings_and_errors.py index 7390cc45f..f4fa38293 100644 --- a/numpy/typing/tests/data/fail/warnings_and_errors.py +++ b/numpy/typing/tests/data/fail/warnings_and_errors.py @@ -1,7 +1,5 @@ import numpy as np -np.AxisError(1.0) # E: Argument 1 to "AxisError" has incompatible type -np.AxisError(1, ndim=2.0) # E: Argument "ndim" to "AxisError" has incompatible type -np.AxisError( - 2, msg_prefix=404 # E: Argument "msg_prefix" to "AxisError" has incompatible type -) +np.AxisError(1.0) # E: No overload variant +np.AxisError(1, ndim=2.0) # E: No overload variant +np.AxisError(2, msg_prefix=404) # E: No overload variant diff --git a/numpy/typing/tests/data/pass/warnings_and_errors.py b/numpy/typing/tests/data/pass/warnings_and_errors.py index 5b6ec2626..a556bf6bc 100644 --- a/numpy/typing/tests/data/pass/warnings_and_errors.py +++ b/numpy/typing/tests/data/pass/warnings_and_errors.py @@ -1,7 +1,6 @@ import numpy as np -np.AxisError(1) +np.AxisError("test") np.AxisError(1, ndim=2) -np.AxisError(1, ndim=None) np.AxisError(1, ndim=2, msg_prefix="error") np.AxisError(1, ndim=2, msg_prefix=None) diff --git a/numpy/typing/tests/data/reveal/warnings_and_errors.py b/numpy/typing/tests/data/reveal/warnings_and_errors.py index c428deb7a..3f20a0135 100644 --- a/numpy/typing/tests/data/reveal/warnings_and_errors.py +++ b/numpy/typing/tests/data/reveal/warnings_and_errors.py @@ -7,4 +7,5 @@ reveal_type(np.VisibleDeprecationWarning()) # E: numpy.VisibleDeprecationWarnin reveal_type(np.ComplexWarning()) # E: numpy.ComplexWarning reveal_type(np.RankWarning()) # E: numpy.RankWarning reveal_type(np.TooHardError()) # E: numpy.TooHardError -reveal_type(np.AxisError(1)) # E: numpy.AxisError +reveal_type(np.AxisError("test")) # E: numpy.AxisError +reveal_type(np.AxisError(5, 1)) # E: numpy.AxisError |
