Let approx() work on more generic sequences

approx() was updated in 9f3122fe to work better with numpy arrays,
however at the same time the requirements were tightened from
requiring an Iterable to requiring a Sequence - the former being
tested only on interface, while the latter requires subclassing or
registration with the abc.

Since the ApproxSequence only used __iter__ and __len__ this commit
reduces the requirement to only what's used, and allows unregistered
Sequence-like containers to be used.

Since numpy arrays qualify for the new criteria, reorder the checks so
that generic sequences are checked for after numpy arrays.
This commit is contained in:
Nicholas Devenish 2018-11-07 12:08:23 +00:00
parent 62967b3110
commit 1a8d9bf254
5 changed files with 23 additions and 6 deletions

View File

@ -163,6 +163,7 @@ Miro Hrončok
Nathaniel Waisbrot Nathaniel Waisbrot
Ned Batchelder Ned Batchelder
Neven Mundar Neven Mundar
Nicholas Devenish
Niclas Olofsson Niclas Olofsson
Nicolas Delaby Nicolas Delaby
Oleg Pidsadnyi Oleg Pidsadnyi

View File

@ -0,0 +1 @@
Loosen the definition of what ``approx`` considers a sequence

View File

@ -45,11 +45,11 @@ MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
if _PY3: if _PY3:
from collections.abc import MutableMapping as MappingMixin from collections.abc import MutableMapping as MappingMixin
from collections.abc import Mapping, Sequence from collections.abc import Iterable, Mapping, Sequence, Sized
else: else:
# those raise DeprecationWarnings in Python >=3.7 # those raise DeprecationWarnings in Python >=3.7
from collections import MutableMapping as MappingMixin # noqa from collections import MutableMapping as MappingMixin # noqa
from collections import Mapping, Sequence # noqa from collections import Iterable, Mapping, Sequence, Sized # noqa
if sys.version_info >= (3, 4): if sys.version_info >= (3, 4):

View File

@ -11,8 +11,9 @@ from six.moves import zip
import _pytest._code import _pytest._code
from _pytest.compat import isclass from _pytest.compat import isclass
from _pytest.compat import Iterable
from _pytest.compat import Mapping from _pytest.compat import Mapping
from _pytest.compat import Sequence from _pytest.compat import Sized
from _pytest.compat import STRING_TYPES from _pytest.compat import STRING_TYPES
from _pytest.outcomes import fail from _pytest.outcomes import fail
@ -182,7 +183,7 @@ class ApproxMapping(ApproxBase):
raise _non_numeric_type_error(self.expected, at="key={!r}".format(key)) raise _non_numeric_type_error(self.expected, at="key={!r}".format(key))
class ApproxSequence(ApproxBase): class ApproxSequencelike(ApproxBase):
""" """
Perform approximate comparisons where the expected value is a sequence of Perform approximate comparisons where the expected value is a sequence of
numbers. numbers.
@ -518,10 +519,14 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
cls = ApproxScalar cls = ApproxScalar
elif isinstance(expected, Mapping): elif isinstance(expected, Mapping):
cls = ApproxMapping cls = ApproxMapping
elif isinstance(expected, Sequence) and not isinstance(expected, STRING_TYPES):
cls = ApproxSequence
elif _is_numpy_array(expected): elif _is_numpy_array(expected):
cls = ApproxNumpy cls = ApproxNumpy
elif (
isinstance(expected, Iterable)
and isinstance(expected, Sized)
and not isinstance(expected, STRING_TYPES)
):
cls = ApproxSequencelike
else: else:
raise _non_numeric_type_error(expected, at=None) raise _non_numeric_type_error(expected, at=None)

View File

@ -496,3 +496,13 @@ class TestApprox(object):
assert actual != approx(expected, rel=5e-8, abs=0) assert actual != approx(expected, rel=5e-8, abs=0)
assert approx(expected, rel=5e-7, abs=0) == actual assert approx(expected, rel=5e-7, abs=0) == actual
assert approx(expected, rel=5e-8, abs=0) != actual assert approx(expected, rel=5e-8, abs=0) != actual
def test_generic_iterable_sized_object(self):
class newIterable(object):
def __iter__(self):
return iter([1, 2, 3, 4])
def __len__(self):
return 4
assert [1, 2, 3, 4] == approx(newIterable())