Skip to content

Commit 0c45497

Browse files
Fix approx when mixing numpy.bool and bool (#13338)
Fix #13047 --------- Co-authored-by: Bruno Oliveira <[email protected]>
1 parent e35f1bf commit 0c45497

File tree

4 files changed

+43
-4
lines changed

4 files changed

+43
-4
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Ashley Whetter
5858
Aviral Verma
5959
Aviv Palivoda
6060
Babak Keyvani
61+
Bahram Farahmand
6162
Barney Gale
6263
Ben Brown
6364
Ben Gartner

changelog/13047.bugfix.rst

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Restore :func:`pytest.approx` handling of equality checks between `bool` and `numpy.bool_` types.
2+
3+
Comparing `bool` and `numpy.bool_` using :func:`pytest.approx` accidentally changed in version `8.3.4` and `8.3.5` to no longer match:
4+
5+
.. code-block:: pycon
6+
7+
>>> import numpy as np
8+
>>> from pytest import approx
9+
>>> [np.True_, np.True_] == pytest.approx([True, True])
10+
False
11+
12+
This has now been fixed:
13+
14+
.. code-block:: pycon
15+
16+
>>> [np.True_, np.True_] == pytest.approx([True, True])
17+
True

src/_pytest/python_api.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -421,23 +421,35 @@ def __repr__(self) -> str:
421421
def __eq__(self, actual) -> bool:
422422
"""Return whether the given value is equal to the expected value
423423
within the pre-specified tolerance."""
424+
425+
def is_bool(val: Any) -> bool:
426+
# Check if `val` is a native bool or numpy bool.
427+
if isinstance(val, bool):
428+
return True
429+
try:
430+
import numpy as np
431+
432+
return isinstance(val, np.bool_)
433+
except ImportError:
434+
return False
435+
424436
asarray = _as_numpy_array(actual)
425437
if asarray is not None:
426438
# Call ``__eq__()`` manually to prevent infinite-recursion with
427439
# numpy<1.13. See #3748.
428440
return all(self.__eq__(a) for a in asarray.flat)
429441

430-
# Short-circuit exact equality, except for bool
431-
if isinstance(self.expected, bool) and not isinstance(actual, bool):
442+
# Short-circuit exact equality, except for bool and np.bool_
443+
if is_bool(self.expected) and not is_bool(actual):
432444
return False
433445
elif actual == self.expected:
434446
return True
435447

436448
# If either type is non-numeric, fall back to strict equality.
437449
# NB: we need Complex, rather than just Number, to ensure that __abs__,
438450
# __sub__, and __float__ are defined. Also, consider bool to be
439-
# nonnumeric, even though it has the required arithmetic.
440-
if isinstance(self.expected, bool) or not (
451+
# non-numeric, even though it has the required arithmetic.
452+
if is_bool(self.expected) or not (
441453
isinstance(self.expected, (Complex, Decimal))
442454
and isinstance(actual, (Complex, Decimal))
443455
):

testing/python/approx.py

+9
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,15 @@ def test_expecting_bool(self) -> None:
647647
assert True != approx(False, abs=2) # noqa: E712
648648
assert 1 != approx(True)
649649

650+
def test_expecting_bool_numpy(self) -> None:
651+
"""Check approx comparing with numpy.bool (#13047)."""
652+
np = pytest.importorskip("numpy")
653+
assert np.False_ != approx(True)
654+
assert np.True_ != approx(False)
655+
assert np.True_ == approx(True)
656+
assert np.False_ == approx(False)
657+
assert np.True_ != approx(False, abs=2)
658+
650659
def test_list(self):
651660
actual = [1 + 1e-7, 2 + 1e-8]
652661
expected = [1, 2]

0 commit comments

Comments
 (0)