Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if python dictionaries are equal, allowing small difference for floats

Tags:

python

pytest

For dictionaries without floating point numbers we are using the simple a == b where a and b are python dictionaries. This works well until we end up with a and b containing floating point numbers somewhere within. They are nested dictionaries so I think that is giving pytest.approx trouble.

What we want is something that will tell us that these two dictionaries are equal (or approximately equal, but something that won't fail only on floating point approximations):

{"foo": {"bar": 0.30000001}} == {"foo": {"bar": 0.30000002}}

pytest.approx() is almost what I want, but it doesn't support nested dictionaries. Is there something out there that can do what I want?

like image 205
maccam912 Avatar asked May 08 '19 17:05

maccam912


1 Answers

You can define your own approximation helper with support for nested dictionaries. Unfortunately, pytest doesn't support enhancement of approx with custom comparators, so you have to write your own function; however, it hasn't be too complicated:

import pytest
from collections.abc import Mapping
from _pytest.python_api import ApproxMapping


def my_approx(expected, rel=None, abs=None, nan_ok=False):
    if isinstance(expected, Mapping):
        return ApproxNestedMapping(expected, rel, abs, nan_ok)
    return pytest.approx(expected, rel, abs, nan_ok)


class ApproxNestedMapping(ApproxMapping):
    def _yield_comparisons(self, actual):
        for k in self.expected.keys():
            if isinstance(actual[k], type(self.expected)):
                gen = ApproxNestedMapping(
                    self.expected[k], rel=self.rel, abs=self.abs, nan_ok=self.nan_ok
                )._yield_comparisons(actual[k])
                for el in gen:
                    yield el
            else:
                yield actual[k], self.expected[k]

    def _check_type(self):
        for key, value in self.expected.items():
            if not isinstance(value, type(self.expected)):
                super()._check_type()

Now use my_approx instead of pytest.approx:

def test_nested():
    assert {'foo': {'bar': 0.30000001}} == my_approx({'foo': {'bar': 0.30000002}})
like image 86
hoefling Avatar answered Sep 21 '22 02:09

hoefling