float 컬렉션에 대한 Python 단위 테스트의 assertAlmostEqual
assertAlmostEqual (X, Y) 에있어서 파이썬 유닛 테스트 워크 시험 여부 x
및 y
정도가 플로트 가정하에 동일하다.
문제 assertAlmostEqual()
는 플로트에서만 작동한다는 것입니다. 나는 assertAlmostEqual()
수레 목록, 수레 집합, 수레 사전, 수레 튜플, 수레 튜플 목록, 수레 목록 집합 등에서 작동 하는 방법을 찾고 있습니다 .
예를 들어 보자 x = 0.1234567890
, y = 0.1234567891
. x
그리고 y
그들은 각자의 마지막 하나를 제외한 숫자를 동의하기 때문에 거의 동일하다. 따라서 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])
.마찬가지로, 두 세트가 거의 동일한 목록으로 변환 될 수 있다면 (각 세트에 순서를 할당하여) 거의 동일한 것으로 간주됩니다.
유사하게, 각 사전의 키 세트가 다른 사전의 키 세트와 거의 같고 거의 동일한 키 쌍마다 해당하는 거의 동일한 값이있는 경우 두 사전은 거의 동일한 것으로 간주됩니다.
일반적으로 : 서로 거의 동일한 일부 부동 소수점을 제외하면 두 컬렉션이 같으면 거의 같다고 생각합니다. 즉, 나는 정말로 객체를 비교하고 싶지만 도중에 부동 소수점을 비교할 때 낮은 (사용자 정의) 정밀도로 비교하고 싶습니다.
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)
함수를 구현 한 방법은 다음과 같습니다 .
먼저 비교해야하는 객체 ( first
및 second
)를 복제하되 정확한 사본을 만들지는 마십시오. 객체 내부에서 만나는 부동 소수점의 중요하지 않은 소수 자릿수를 잘라냅니다.
이제 당신의 사본을 가지고 first
와 second
하찮은 진수가 사라있는 위해를 바로 비교 first
및 second
사용하여 ==
연산자를.
cut_insignificant_digits_recursively(obj, places)
중복 obj
되지만 places
원래의 각 float의 최상위 십진수 만 남는 함수 가 있다고 가정 해 봅시다 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
코드 및 단위 테스트는 https://github.com/snakile/approximate_comparator에서 확인할 수 있습니다 . 개선 및 버그 수정을 환영합니다.
파이썬 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 )
numpy
패키지를 사용해도 괜찮다 numpy.testing
면 assert_array_almost_equal
방법이 있습니다.
이것은 array_like
객체에 대해 작동 하므로 float의 배열, 목록 및 튜플에는 적합하지만 세트 및 사전에는 작동하지 않습니다.
문서는 여기에 있습니다 .
그러한 방법은 없습니다. 직접해야합니다.
목록과 튜플의 경우 정의는 분명하지만 언급 한 다른 경우는 분명하지 않으므로 그러한 함수가 제공되지 않는 것은 당연합니다. 예를 들어, {1.00001: 1.00002}
거의 같 {1.00002: 1.00001}
습니까? 이러한 경우를 처리하려면 친밀도가 키나 값 또는 둘 다에 따라 달라지는 지 여부를 선택해야합니다. 집합의 경우 집합이 순서가 지정되지 않았으므로 의미있는 정의를 찾을 가능성이 낮으므로 "해당하는"요소에 대한 개념이 없습니다.
직접 구현해야 할 수도 있지만 목록과 세트는 동일한 방식으로 반복 될 수 있지만 사전은 다른 이야기이고 값이 아닌 키를 반복하며 세 번째 예는 저에게 약간 모호해 보입니다. 세트 내의 각 값 또는 각 세트의 각 값을 비교합니다.
여기에 간단한 코드 스 니펫이 있습니다.
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))
다른 방법은 예를 들어 각 부동 소수점을 고정 정밀도의 문자열로 변환하여 데이터를 유사한 형식으로 변환하는 것입니다.
def comparable(data):
"""Converts `data` to a comparable structure by converting any floats to a string with fixed precision."""
if isinstance(data, (int, str)):
return data
if isinstance(data, float):
return '{:.4f}'.format(data)
if isinstance(data, list):
return [comparable(el) for el in data]
if isinstance(data, tuple):
return tuple([comparable(el) for el in data])
if isinstance(data, dict):
return {k: comparable(v) for k, v in data.items()}
그런 다음 다음을 수행 할 수 있습니다.
self.assertEquals(comparable(value1), comparable(value2))
이 답변 중 어느 것도 나를 위해 작동하지 않습니다. 다음 코드는 파이썬 컬렉션, 클래스, 데이터 클래스 및 명명 된 튜플에 대해 작동합니다. 나는 뭔가를 잊었을 수도 있지만 지금까지 이것은 나를 위해 작동합니다.
import unittest
from collections import namedtuple, OrderedDict
from dataclasses import dataclass
from typing import Any
def are_almost_equal(o1: Any, o2: Any, max_abs_ratio_diff: float, max_abs_diff: float) -> bool:
"""
Compares two objects by recursively walking them trough. Equality is as usual except for floats.
Floats are compared according to the two measures defined below.
:param o1: The first object.
:param o2: The second object.
:param max_abs_ratio_diff: The maximum allowed absolute value of the difference.
`abs(1 - (o1 / o2)` and vice-versa if o2 == 0.0. Ignored if < 0.
:param max_abs_diff: The maximum allowed absolute difference `abs(o1 - o2)`. Ignored if < 0.
:return: Whether the two objects are almost equal.
"""
if type(o1) != type(o2):
return False
composite_type_passed = False
if hasattr(o1, '__slots__'):
if len(o1.__slots__) != len(o2.__slots__):
return False
if any(not are_almost_equal(getattr(o1, s1), getattr(o2, s2),
max_abs_ratio_diff, max_abs_diff)
for s1, s2 in zip(sorted(o1.__slots__), sorted(o2.__slots__))):
return False
else:
composite_type_passed = True
if hasattr(o1, '__dict__'):
if len(o1.__dict__) != len(o2.__dict__):
return False
if any(not are_almost_equal(k1, k2, max_abs_ratio_diff, max_abs_diff)
or not are_almost_equal(v1, v2, max_abs_ratio_diff, max_abs_diff)
for ((k1, v1), (k2, v2))
in zip(sorted(o1.__dict__.items()), sorted(o2.__dict__.items()))
if not k1.startswith('__')): # avoid infinite loops
return False
else:
composite_type_passed = True
if isinstance(o1, dict):
if len(o1) != len(o2):
return False
if any(not are_almost_equal(k1, k2, max_abs_ratio_diff, max_abs_diff)
or not are_almost_equal(v1, v2, max_abs_ratio_diff, max_abs_diff)
for ((k1, v1), (k2, v2)) in zip(sorted(o1.items()), sorted(o2.items()))):
return False
elif any(issubclass(o1.__class__, c) for c in (list, tuple, set)):
if len(o1) != len(o2):
return False
if any(not are_almost_equal(v1, v2, max_abs_ratio_diff, max_abs_diff)
for v1, v2 in zip(o1, o2)):
return False
elif isinstance(o1, float):
if o1 == o2:
return True
else:
if max_abs_ratio_diff > 0: # if max_abs_ratio_diff < 0, max_abs_ratio_diff is ignored
if o2 != 0:
if abs(1.0 - (o1 / o2)) > max_abs_ratio_diff:
return False
else: # if both == 0, we already returned True
if abs(1.0 - (o2 / o1)) > max_abs_ratio_diff:
return False
if 0 < max_abs_diff < abs(o1 - o2): # if max_abs_diff < 0, max_abs_diff is ignored
return False
return True
else:
if not composite_type_passed:
return o1 == o2
return True
class EqualityTest(unittest.TestCase):
def test_floats(self) -> None:
o1 = ('hi', 3, 3.4)
o2 = ('hi', 3, 3.400001)
self.assertTrue(are_almost_equal(o1, o2, 0.0001, 0.0001))
self.assertFalse(are_almost_equal(o1, o2, 0.00000001, 0.00000001))
def test_ratio_only(self):
o1 = ['hey', 10000, 123.12]
o2 = ['hey', 10000, 123.80]
self.assertTrue(are_almost_equal(o1, o2, 0.01, -1))
self.assertFalse(are_almost_equal(o1, o2, 0.001, -1))
def test_diff_only(self):
o1 = ['hey', 10000, 1234567890.12]
o2 = ['hey', 10000, 1234567890.80]
self.assertTrue(are_almost_equal(o1, o2, -1, 1))
self.assertFalse(are_almost_equal(o1, o2, -1, 0.1))
def test_both_ignored(self):
o1 = ['hey', 10000, 1234567890.12]
o2 = ['hey', 10000, 0.80]
o3 = ['hi', 10000, 0.80]
self.assertTrue(are_almost_equal(o1, o2, -1, -1))
self.assertFalse(are_almost_equal(o1, o3, -1, -1))
def test_different_lengths(self):
o1 = ['hey', 1234567890.12, 10000]
o2 = ['hey', 1234567890.80]
self.assertFalse(are_almost_equal(o1, o2, 1, 1))
def test_classes(self):
class A:
d = 12.3
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
o1 = A(2.34, 'str', {1: 'hey', 345.23: [123, 'hi', 890.12]})
o2 = A(2.34, 'str', {1: 'hey', 345.231: [123, 'hi', 890.121]})
self.assertTrue(are_almost_equal(o1, o2, 0.1, 0.1))
self.assertFalse(are_almost_equal(o1, o2, 0.0001, 0.0001))
o2.hello = 'hello'
self.assertFalse(are_almost_equal(o1, o2, -1, -1))
def test_namedtuples(self):
B = namedtuple('B', ['x', 'y'])
o1 = B(3.3, 4.4)
o2 = B(3.4, 4.5)
self.assertTrue(are_almost_equal(o1, o2, 0.2, 0.2))
self.assertFalse(are_almost_equal(o1, o2, 0.001, 0.001))
def test_classes_with_slots(self):
class C(object):
__slots__ = ['a', 'b']
def __init__(self, a, b):
self.a = a
self.b = b
o1 = C(3.3, 4.4)
o2 = C(3.4, 4.5)
self.assertTrue(are_almost_equal(o1, o2, 0.3, 0.3))
self.assertFalse(are_almost_equal(o1, o2, -1, 0.01))
def test_dataclasses(self):
@dataclass
class D:
s: str
i: int
f: float
@dataclass
class E:
f2: float
f4: str
d: D
o1 = E(12.3, 'hi', D('hello', 34, 20.01))
o2 = E(12.1, 'hi', D('hello', 34, 20.0))
self.assertTrue(are_almost_equal(o1, o2, -1, 0.4))
self.assertFalse(are_almost_equal(o1, o2, -1, 0.001))
o3 = E(12.1, 'hi', D('ciao', 34, 20.0))
self.assertFalse(are_almost_equal(o2, o3, -1, -1))
def test_ordereddict(self):
o1 = OrderedDict({1: 'hey', 345.23: [123, 'hi', 890.12]})
o2 = OrderedDict({1: 'hey', 345.23: [123, 'hi', 890.0]})
self.assertTrue(are_almost_equal(o1, o2, 0.01, -1))
self.assertFalse(are_almost_equal(o1, o2, 0.0001, -1))
나는 여전히 self.assertEqual()
똥이 팬을 때릴 때 가장 유익한 정보를 유지 하기 위해 사용할 것 입니다. 예를 들어 반올림하여 수행 할 수 있습니다.
self.assertEqual(round_tuple((13.949999999999999, 1.121212), 2), (13.95, 1.12))
곳 round_tuple
이다
def round_tuple(t: tuple, ndigits: int) -> tuple:
return tuple(round(e, ndigits=ndigits) for e in t)
def round_list(l: list, ndigits: int) -> list:
return [round(e, ndigits=ndigits) for e in l]
파이썬 문서에 따르면 (참조 https://stackoverflow.com/a/41407651/1031191을 때문에, 13.94999999 같은 문제를 반올림 멀리 얻을 수 있습니다) 13.94999999 == 13.95
입니다 True
.
'Development Tip' 카테고리의 다른 글
기본 제공 검색 / 필터 상자를 사용하지 않고 jqGrid 데이터를 필터링하는 방법 (0) | 2020.12.12 |
---|---|
핸들 바-부분적으로 부모 컨텍스트에 액세스 할 수 있습니까? (0) | 2020.12.12 |
날짜 시간 x 시리즈에 대한 엑셀 플롯 (0) | 2020.12.12 |
플라스크에서 ajax에서 게시 된 데이터를 어떻게 사용할 수 있습니까? (0) | 2020.12.12 |
선행 공백을 무시하면서 2 개의 파일을 어떻게 비교할 수 있습니까? (0) | 2020.12.12 |