Skip to content

[8.1.x] Fixed Bug Regarding Attribute Error in pytest.approx For Types Implicitly Convertible to Numpy Arrays #12240

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

Merged
merged 2 commits into from
Apr 25, 2024
Merged
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ Pierre Sassoulas
Pieter Mulder
Piotr Banaszkiewicz
Piotr Helm
Poulami Sau
Prakhar Gurunani
Prashant Anand
Prashant Sharma
Expand Down
1 change: 1 addition & 0 deletions changelog/12114.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed error in :func:`pytest.approx` when used with `numpy` arrays and comparing with other types.
14 changes: 9 additions & 5 deletions src/_pytest/python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def __repr__(self) -> str:
)
return f"approx({list_scalars!r})"

def _repr_compare(self, other_side: "ndarray") -> List[str]:
def _repr_compare(self, other_side: Union["ndarray", List[Any]]) -> List[str]:
import itertools
import math

Expand All @@ -163,10 +163,14 @@ def get_value_from_nested_list(
self._approx_scalar, self.expected.tolist()
)

if np_array_shape != other_side.shape:
# Convert other_side to numpy array to ensure shape attribute is available.
other_side_as_array = _as_numpy_array(other_side)
assert other_side_as_array is not None

if np_array_shape != other_side_as_array.shape:
return [
"Impossible to compare arrays with different shapes.",
f"Shapes: {np_array_shape} and {other_side.shape}",
f"Shapes: {np_array_shape} and {other_side_as_array.shape}",
]

number_of_elements = self.expected.size
Expand All @@ -175,7 +179,7 @@ def get_value_from_nested_list(
different_ids = []
for index in itertools.product(*(range(i) for i in np_array_shape)):
approx_value = get_value_from_nested_list(approx_side_as_seq, index)
other_value = get_value_from_nested_list(other_side, index)
other_value = get_value_from_nested_list(other_side_as_array, index)
if approx_value != other_value:
abs_diff = abs(approx_value.expected - other_value)
max_abs_diff = max(max_abs_diff, abs_diff)
Expand All @@ -188,7 +192,7 @@ def get_value_from_nested_list(
message_data = [
(
str(index),
str(get_value_from_nested_list(other_side, index)),
str(get_value_from_nested_list(other_side_as_array, index)),
str(get_value_from_nested_list(approx_side_as_seq, index)),
)
for index in different_ids
Expand Down
18 changes: 18 additions & 0 deletions testing/python/approx.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,24 @@ def test_numpy_array_wrong_shape(self):
assert a12 != approx(a21)
assert a21 != approx(a12)

def test_numpy_array_implicit_conversion(self) -> None:
"""#12114."""
np = pytest.importorskip("numpy")

class ImplicitArray:
"""Type which is implicitly convertible to a numpy array."""

def __init__(self, vals):
self.vals = vals

def __array__(self, dtype=None, copy=None):
return np.array(self.vals)

vec1 = ImplicitArray([1.0, 2.0, 3.0])
vec2 = ImplicitArray([1.0, 2.0, 4.0])
# see issue #12114 for test case
assert vec1 != approx(vec2)

def test_numpy_array_protocol(self):
"""
array-like objects such as tensorflow's DeviceArray are handled like ndarray.
Expand Down