Die assertAlmostEqual (x, y) -Methode in Pythons Unit-Testing-Framework testet, ob x
und y
ungefähr gleich, vorausgesetzt, es handelt sich um Floats.
Das Problem dabei assertAlmostEqual()
ist, dass es nur auf Floats funktioniert. Ich suche nach einer Methode, assertAlmostEqual()
die auf Float-Listen, Float-Sätzen, Float-Wörterbüchern, Float-Tupeln, Float-Tupel-Listen, Float-Listen, Float-Listen usw. funktioniert.
Zum Beispiel lassen Sie x = 0.1234567890
, y = 0.1234567891
. x
und y
sind fast gleich, weil sie mit Ausnahme der letzten auf jeder einzelnen Ziffer übereinstimmen. Deshalb self.assertAlmostEqual(x, y)
liegt es True
daran, assertAlmostEqual()
für Schwimmer zu arbeiten.
Ich suche nach einem allgemeineren, assertAlmostEquals()
der auch die folgenden Aufrufe an bewertet 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)])
.
Gibt es eine solche Methode oder muss ich sie selbst implementieren?
Erläuterungen:
assertAlmostEquals()
hat einen optionalen Parameter mit dem Namenplaces
und die Zahlen werden verglichen, indem die auf die Dezimalzahl gerundete Differenz berechnet wirdplaces
. Standardmäßigplaces=7
ist daherself.assertAlmostEqual(0.5, 0.4)
False undself.assertAlmostEqual(0.12345678, 0.12345679)
True. Mein SpekulativassertAlmostEqual_generic()
sollte die gleiche Funktionalität haben.Zwei Listen gelten als nahezu gleich, wenn sie in genau derselben Reihenfolge fast gleiche Nummern haben. formal ,
for i in range(n): self.assertAlmostEqual(list1[i], list2[i])
.In ähnlicher Weise werden zwei Sätze als nahezu gleich angesehen, wenn sie in nahezu gleiche Listen konvertiert werden können (indem jedem Satz eine Reihenfolge zugewiesen wird).
In ähnlicher Weise werden zwei Wörterbücher als nahezu gleich angesehen, wenn der Schlüsselsatz jedes Wörterbuchs fast gleich dem Schlüsselsatz des anderen Wörterbuchs ist, und für jedes dieser nahezu gleichen Schlüsselpaare gibt es einen entsprechenden nahezu gleichen Wert.
Im Allgemeinen: Ich halte zwei Sammlungen für fast gleich, wenn sie gleich sind, mit Ausnahme einiger entsprechender Floats, die fast gleich sind. Mit anderen Worten, ich möchte Objekte wirklich vergleichen, aber mit einer geringen (benutzerdefinierten) Genauigkeit, wenn ich Floats auf dem Weg vergleiche.
quelle
float
Schlüssel im Wörterbuch zu verwenden? Da Sie nicht sicher sein können, genau den gleichen Float zu erhalten, werden Sie Ihre Artikel niemals mithilfe der Suche finden. Und wenn Sie keine Suche verwenden, warum nicht einfach eine Liste von Tupeln anstelle eines Wörterbuchs verwenden? Das gleiche Argument gilt für Mengen.assertAlmostEqual
.Antworten:
Wenn es Ihnen nichts ausmacht, NumPy (das mit Ihrem Python (x, y) geliefert wird) zu verwenden, sollten Sie sich das
np.testing
Modul ansehen , das unter anderem eineassert_almost_equal
Funktion definiert.Die Unterschrift ist
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)
quelle
numpy.testing
fast gleiche Methoden funktionieren nur für Zahlen, Arrays, Tupel und Listen. Sie arbeiten nicht mit Wörterbüchern, Sets und Sammlungen von Sammlungen.np.testing.assert_equal
erkennt beispielsweise Wörterbücher als Argumente an (auch wenn der Vergleich von a durchgeführt wird,==
was für Sie nicht funktioniert).assert_array_almost_equal
empfohlen wirdassert_allclose
,assert_array_almost_equal_nulp
oderassert_array_max_ulp
stattdessen zu verwenden.Ab Python 3.5 können Sie mit vergleichen
math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)
Wie in pep-0485 beschrieben . Die Implementierung sollte äquivalent zu sein
abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
quelle
So habe ich eine generische
is_almost_equal(first, second)
Funktion implementiert :Duplizieren Sie zunächst die Objekte, die Sie vergleichen möchten (
first
undsecond
), aber erstellen Sie keine exakte Kopie: Schneiden Sie die unbedeutenden Dezimalstellen aller Floats aus, auf die Sie im Objekt stoßen.Nachdem Sie Kopien von
first
und haben,second
für die die unbedeutenden Dezimalstellen weg sind, vergleichen Sie einfach den Operatorfirst
undsecond
verwenden Sie ihn==
.Nehmen wir an, wir haben eine
cut_insignificant_digits_recursively(obj, places)
Funktion, die dupliziert,obj
aber nur dieplaces
höchstwertigen Dezimalstellen jedes Floats im Original belässtobj
. Hier ist eine funktionierende Implementierung vonis_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
Und hier ist eine funktionierende Implementierung von
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
Der Code und seine Komponententests sind hier verfügbar: https://github.com/snakile/approximate_comparator . Ich freue mich über jede Verbesserung und Fehlerbehebung.
quelle
fmt="{{0:{0}f}}".format(decimals)
, und verwenden Sie diesesfmt
Format, um Ihre Floats zu "stringifizieren"?places
Gibt die Anzahl der Dezimalstellen an, nicht die Anzahl der signifikanten Zahlen. Zum Beispiel sollte ein Vergleich von1024.123
und1023.999
mit 3 signifikanten Werten gleich sein, aber mit 3 Dezimalstellen sind sie nicht gleich.Wenn es Ihnen nichts ausmacht, das
numpy
Paket zu verwenden, dannnumpy.testing
haben Sie dieassert_array_almost_equal
Methode.Dies funktioniert für
array_like
Objekte, ist also für Arrays, Listen und Tupel von Floats in Ordnung, funktioniert jedoch nicht für Mengen und Wörterbücher.Die Dokumentation finden Sie hier .
quelle
Es gibt keine solche Methode, Sie müssten es selbst tun.
Bei Listen und Tupeln ist die Definition offensichtlich. Beachten Sie jedoch, dass die anderen von Ihnen genannten Fälle nicht offensichtlich sind. Kein Wunder, dass eine solche Funktion nicht bereitgestellt wird. Ist zum Beispiel
{1.00001: 1.00002}
fast gleich{1.00002: 1.00001}
? Um solche Fälle zu behandeln, muss entschieden werden, ob die Nähe von Schlüsseln oder Werten oder von beiden abhängt. Für Mengen ist es unwahrscheinlich, dass Sie eine aussagekräftige Definition finden, da die Mengen ungeordnet sind und daher keine Vorstellung von "entsprechenden" Elementen besteht.quelle
{1.00001: 1.00002}
fast{1.00002: 1.00001}
genau dann fast gleich ist, wenn 1,00001 fast gleich 1,00002 ist. Standardmäßig sind sie nicht fast gleich (da die Standardgenauigkeit 7 Dezimalstellen beträgt), aber für einen ausreichend kleinen Wert sindplaces
sie fast gleich.float
in Diktat sollte aus offensichtlichen Gründen nicht empfohlen (und möglicherweise sogar verboten) werden. Die ungefähre Gleichheit des Diktats sollte nur auf Werten basieren. Das Testframework muss sich nicht um die falsche Verwendung vonfloat
for-Schlüsseln kümmern . Für Sets können sie vor dem Vergleich sortiert und sortierte Listen verglichen werden.Möglicherweise müssen Sie es selbst implementieren, obwohl es stimmt, dass Liste und Mengen auf dieselbe Weise iteriert werden können, Wörterbücher eine andere Geschichte sind, Sie ihre Schlüssel und keine Werte iterieren und das dritte Beispiel mir etwas mehrdeutig erscheint, meinen Sie das? Vergleichen Sie jeden Wert innerhalb des Satzes oder jeden Wert aus jedem Satz.
Hier ist ein einfaches Code-Snippet.
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))
quelle
Keine dieser Antworten funktioniert für mich. Der folgende Code sollte für Python-Sammlungen, Klassen, Datenklassen und Namedtuples funktionieren. Ich hätte vielleicht etwas vergessen, aber bisher funktioniert das für mich.
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))
quelle
Ich würde immer noch dafür verwenden,
self.assertEqual()
dass es am informativsten bleibt, wenn Scheiße den Fan trifft. Sie können dies durch Runden tun, z.self.assertEqual(round_tuple((13.949999999999999, 1.121212), 2), (13.95, 1.12))
wo
round_tuple
istdef 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]
Nach Ansicht der Python - Dokumentation (siehe https://stackoverflow.com/a/41407651/1031191 ) Sie mit Rundungs Themen wie 13,94999999 weg erhalten können, da
13.94999999 == 13.95
istTrue
.quelle
Ein alternativer Ansatz besteht darin, Ihre Daten in eine vergleichbare Form zu konvertieren, indem Sie beispielsweise jeden Float mit fester Genauigkeit in einen String verwandeln.
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()}
Dann kannst du:
quelle