Python中的assertAlmostEqual对浮点数集合进行unit testing

Python的unit testing框架中的assertAlmostEqual(x,y)方法testingxy是否大致相等,假设它们是浮点数。

assertAlmostEqual()的问题是它只能在浮点数上运行。 我正在寻找像assertAlmostEqual()这样的方法,它在浮点,浮点集,浮点字典,浮点元组,浮点元组列表,浮点列表集合等列表上工作。

例如,令x = 0.1234567890y = 0.1234567891xy几乎相等,因为除了最后一个数字外,他们都同意每个数字。 因此, self.assertAlmostEqual(x, y)True因为assertAlmostEqual()适用于浮点数。

我正在寻找一个更通用的assertAlmostEquals() ,也评估以下调用True

  • self.assertAlmostEqual_generic([x, x, x], [y, y, y])
  • self.assertAlmostEqual_generic({1: x, 2: x, 3: x}, {1: y, 2: y, 3: y})
  • self.assertAlmostEqual_generic([(x,x)], [(y,y)])

有没有这样的方法,还是我自己要实现呢?

澄清:

  • assertAlmostEquals()有一个名为places的可选参数,通过计算舍入到小数位数的差值来比较数字。 默认情况下, places=7 ,因此self.assertAlmostEqual(0.5, 0.4)为False,而self.assertAlmostEqual(0.12345678, 0.12345679)为True。 我推测assertAlmostEqual_generic()应该有相同的function。

  • 如果两个列表中的数字完全相同,则认为它们几乎相等。 正式地, for i in range(n): self.assertAlmostEqual(list1[i], list2[i])

  • 同样的,如果两个集合可以转换成几乎相同的列表(通过给每个集合分配一个命令),则认为两个集合几乎相等。

  • 类似地,如果每个字典的密钥集几乎等于另一个字典的密钥集,则认为两个字典几乎相等,并且对于每个这样的几乎相等的密钥对,存在对应的几乎相等的值。

  • 一般来说:如果两个集合相等,我认为两个集合几乎相等,除了一些对应的浮动几乎相等。 换句话说,我想真正比较一下对象,但是在比较浮动的时候,它们的精确度很低(定制的)。

如果您不介意使用NumPy(Python(x,y)附带),您可能需要查看np.testing模块,其中定义了assert_almost_equal函数。

签名是np.testing.assert_almost_equal(actual, desired, decimal=7, err_msg='', verbose=True)

 >>> x = 1.000001 >>> y = 1.000002 >>> np.testing.assert_almost_equal(x, y) AssertionError: Arrays are not almost equal to 7 decimals ACTUAL: 1.000001 DESIRED: 1.000002 >>> np.testing.assert_almost_equal(x, y, 5) >>> np.testing.assert_almost_equal([x, x, x], [y, y, y], 5) >>> np.testing.assert_almost_equal((x, x, x), (y, y, y), 5) 

以下是我已经实现了一个通用的is_almost_equal(first, second)函数

首先,复制需要比较的对象( firstsecond ),但不要做一个精确的副本:剪切在对象内遇到的任何浮点数的无意义的十进制数字。

现在您已经有了firstsecond个小数位不重要的副本,只需使用==运算符比较firstsecond数字即可。

假设我们有一个复制objcut_insignificant_digits_recursively(obj, places)函数,但只保留原始obj中每个浮点数的最重要的十进制数字。 这是一个is_almost_equals(first, second, places)的工作实现:

 from insignificant_digit_cutter import cut_insignificant_digits_recursively def is_almost_equal(first, second, places): '''returns True if first and second equal. returns true if first and second aren't equal but have exactly the same structure and values except for a bunch of floats which are just almost equal (floats are almost equal if they're equal when we consider only the [places] most significant digits of each).''' if first == second: return True cut_first = cut_insignificant_digits_recursively(first, places) cut_second = cut_insignificant_digits_recursively(second, places) return cut_first == cut_second 

以下是cut_insignificant_digits_recursively(obj, places)的一个工作实现:

 def cut_insignificant_digits(number, places): '''cut the least significant decimal digits of a number, leave only [places] decimal digits''' if type(number) != float: return number number_as_str = str(number) end_of_number = number_as_str.find('.')+places+1 if end_of_number > len(number_as_str): return number return float(number_as_str[:end_of_number]) def cut_insignificant_digits_lazy(iterable, places): for obj in iterable: yield cut_insignificant_digits_recursively(obj, places) def cut_insignificant_digits_recursively(obj, places): '''return a copy of obj except that every float loses its least significant decimal digits remaining only [places] decimal digits''' t = type(obj) if t == float: return cut_insignificant_digits(obj, places) if t in (list, tuple, set): return t(cut_insignificant_digits_lazy(obj, places)) if t == dict: return {cut_insignificant_digits_recursively(key, places): cut_insignificant_digits_recursively(val, places) for key,val in obj.items()} return obj 

代码和它的unit testing可以在这里find: https : //github.com/snakile/approximate_comparator 。 我欢迎任何改进和错误修复。

没有这样的方法,你必须自己做。

对于列表和元组来说,定义是显而易见的,但是请注意,您提到的其他情况并不明显,所以难怪没有提供这样的函数。 例如, {1.00001: 1.00002}几乎等于{1.00002: 1.00001} ? 处理这种情况需要select是否靠近键或值或两者兼而有之。 对于集合,你不可能find有意义的定义,因为集合是无序的,所以没有“相应”元素的概念。

如果你不介意使用numpy包,那么numpy.testing就有assert_array_almost_equal方法。

这适用于array_like对象,所以对于数组,浮点数的列表和元组来说没问题,但是它不适用于集合和字典。

文档在这里 。

从python 3.5开始,你可以比较一下

 math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0) 

如pep-0485所述 。 实施应该等同于

 abs(ab) <= max( rel_tol * max(abs(a), abs(b)), abs_tol ) 

你可能必须自己实现它,而列表和集合的真实性可以用同样的方式迭代,字典是不同的故事,你迭代他们的键而不是价值观,第三个例子似乎有点模糊,你的意思是比较集合中的每个值或每个集合的每个值。

inheritance人一个简单的代码片段。

 def almost_equal(value_1, value_2, accuracy = 10**-8): return abs(value_1 - value_2) < accuracy x = [1,2,3,4] y = [1,2,4,5] assert all(almost_equal(*values) for values in zip(x, y))