2012-08-27 45 views
41

assertAlmostEqual(x, y)方法Python's unit testing framework測試xy假設它們是浮點數,它們大致相等。assertAlmostEqual在Python單元測試中用於浮點數集合

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數之差進行比較。默認places=7,因此self.assertAlmostEqual(0.5, 0.4)是False而self.assertAlmostEqual(0.12345678, 0.12345679)是True。我的推測assertAlmostEqual_generic()應該有相同的功能。

  • 如果兩個列表具有幾乎相同的數字,則兩個列表幾乎相同。正式,for i in range(n): self.assertAlmostEqual(list1[i], list2[i])

  • 類似地,如果兩個集合可以轉換爲幾乎相同的列表(通過爲每個集合分配一個訂單),則認爲兩個集合幾乎相等。

  • 類似地,如果每個字典的密鑰集幾乎等於另一個字典的密鑰集,則認爲兩個字典幾乎相等,並且對於每個這樣幾乎相等的密鑰對,存在對應的幾乎相等的值。

  • 一般來說:我認爲兩個集合幾乎相等,如果它們相等的話,除了一些相應的花車幾乎相等。換句話說,我想真正比較一下對象,但是在比較浮動的過程中比較低(定製的)精度。

回答

6

下面是我實現一個通用的is_almost_equal(first, second)功能

首先,複製你需要比較(firstsecond)的對象,但不要」 t精確複製:剪切您在對象內遇到的任何浮點數的無意義的十進制數字。

現在,你有firstsecond爲這微不足道的小數位數都不見了副本,只是用==操作比較firstsecond

我們假設我們有一個cut_insignificant_digits_recursively(obj, places)函數,該函數複製obj,但只保留原始obj中每個浮點數的places最高有效小數位數。下面是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 

的代碼和它的單元測試都可以在這裏:https://github.com/snakile/approximate_comparator。我歡迎任何改進和錯誤修復。

+0

而不是比較浮動,你比較字符串?好的...但是,那麼設置一個通用格式會不會更容易?像'fmt =「{{0:{0} f}}」.format(decimals)',並使用這個'fmt'格式來「串化」你的浮點數? –

+0

(我知道這是一個味道的問題,但我發現與if幾乎不可讀的同一行上的單個語句)。 –

+0

那麼,當集合沒有順序時,你對列表,元組和集合使用相同的MO?! –

3

有沒有這樣的方法,你必須自己動手。

對於列表和元組,定義是顯而易見的,但請注意,您提到的其他情況並不明顯,所以難怪沒有提供這樣的函數。例如,是{1.00001: 1.00002}幾乎等於{1.00002: 1.00001}?處理這種情況需要做出關於親密度取決於鍵或值還是兩者的選擇。對於集合,你不可能找到有意義的定義,因爲集合是無序的,所以沒有「相應」元素的概念。

+0

'{1.00001:1.00002]'錯字? –

+0

修復它,謝謝。 – BrenBarn

+0

BrenBarn:我已經添加了對這個問題的解釋。你的問題的答案是,當且僅當1.00001幾乎等於1.00002時,「{1.00001:1.00002}」幾乎等於「{1.00002:1.00001}」。默認情況下,它們幾乎不相等(因爲默認精度爲7位小數),但對於「places」的足夠小的值,它們幾乎相等。 – snakile

0

你可能必須自己實現它,而列表和集合的確實可以用同樣的方式迭代,字典是不同的故事,你迭代他們的鍵而不是值,第三個例子對我來說似乎有點模糊,你的意思是比較集合中的每個值,或每個集合中的每個值。

繼承人一個簡單的代碼片段。

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)) 
+0

謝謝,解決方案對於列表和元組是正確的,但不適用於其他類型的集合(或嵌套集合)。查看我添加到問題中的說明。我希望我的意圖現在是清楚的。如果在一個沒有非常精確地測量數字的世界裏它們被認爲是平等的,那麼兩套幾乎是相等的。 – snakile

38

,如果你不介意使用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) 
+0

這很接近,但'numpy.testing'幾乎相等的方法僅適用於數字,數組,元組和列表。他們不用於字典,集合和集合。 – snakile

+0

的確,但這是一個開始。此外,你可以訪問你可以修改的源代碼,以便比較字典,集合等等。例如,'np.testing.assert_equal'確實可以將字典識別爲參數(即使比較是通過'=='完成的,這對您不起作用)。 –

+0

當然,在比較套餐時,你仍然會遇到麻煩,就像@BrenBarn提到的那樣。 –

4

如果你不介意使用numpy包,那麼numpy.testingassert_array_almost_equal方法。

這適用於array_like對象,因此它適用於數組,浮點數的列表和元組,但它不適用於集和字典。

該文檔是here

2

對於Python 3.5,你可以比較使用

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

pep-0485描述。 執行應該相當於

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