run - unittest python 3
assertAlmostEqual en Python unit-test para colecciones de flotadores (7)
El método assertAlmostEqual (x, y) en el marco de prueba de unidades de Python comprueba si x
y y
son aproximadamente iguales, asumiendo que son flotantes.
El problema con assertAlmostEqual()
es que solo funciona en flotadores. Estoy buscando un método como assertAlmostEqual()
que funciona en listas de flotadores, conjuntos de flotadores, diccionarios de flotadores, tuplas de flotadores, listas de tuplas de flotadores, conjuntos de listas de flotadores, etc.
Por ejemplo, sea x = 0.1234567890
, y = 0.1234567891
. x
e y
son casi iguales porque están de acuerdo en todos y cada uno de los dígitos, excepto el último. Por self.assertAlmostEqual(x, y)
tanto, self.assertAlmostEqual(x, y)
es True
porque assertAlmostEqual()
funciona para flotadores.
Estoy buscando un assertAlmostEquals()
más genérico assertAlmostEquals()
que también evalúa las siguientes llamadas a 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)])
.
¿Existe tal método o tengo que implementarlo yo mismo?
Aclaraciones:
assertAlmostEquals()
tiene un parámetro opcional llamadoplaces
y los números se comparan calculando la diferencia redondeada al número deplaces
decimales. Por defecto, losplaces=7
, porself.assertAlmostEqual(0.5, 0.4)
tantoself.assertAlmostEqual(0.5, 0.4)
es False, mientras queself.assertAlmostEqual(0.12345678, 0.12345679)
es True. MiassertAlmostEqual_generic()
especulativo debería tener la misma funcionalidad.Dos listas se consideran casi iguales si tienen números casi iguales en exactamente el mismo orden. formalmente,
for i in range(n): self.assertAlmostEqual(list1[i], list2[i])
.De manera similar, dos conjuntos se consideran casi iguales si se pueden convertir en listas casi iguales (asignando un orden a cada conjunto).
De manera similar, dos diccionarios se consideran casi iguales si el conjunto de claves de cada diccionario es casi igual al conjunto de claves del otro diccionario, y para cada uno de esos pares de claves casi iguales hay un valor casi igual correspondiente.
En general, considero que dos colecciones son casi iguales si son iguales, excepto por algunas flotillas correspondientes que son casi iguales entre sí. En otras palabras, me gustaría realmente comparar objetos pero con una precisión baja (personalizada) al comparar flotantes en el camino.
A partir de Python 3.5 puedes comparar usando
math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)
Como se describe en pep-0485 . La implementación debe ser equivalente a
abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
Es posible que tenga que implementarlo usted mismo, mientras que es cierto que la lista y los conjuntos se pueden iterar de la misma manera, los diccionarios son una historia diferente, se iteran sus valores y sus claves, y el tercer ejemplo me parece un poco ambiguo. compare cada valor dentro del conjunto, o cada valor de cada conjunto.
Heres un simple fragmento de código.
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))
No hay tal método, tendrías que hacerlo tú mismo.
Para listas y tuplas, la definición es obvia, pero tenga en cuenta que los otros casos que menciona no son obvios, por lo que no es de extrañar que no se proporcione dicha función. Por ejemplo, ¿es {1.00001: 1.00002}
casi igual a {1.00002: 1.00001}
? El manejo de tales casos requiere elegir si la cercanía depende de claves o valores o de ambos. Para los conjuntos, es poco probable que encuentre una definición significativa, ya que los conjuntos no están ordenados, por lo que no hay una noción de elementos "correspondientes".
Si no le importa usar NumPy (que viene con su Python (x, y)), es posible que desee ver el módulo np.testing
que define, entre otros, una función assert_almost_equal
.
La firma es 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)
Si no le importa usar el paquete numpy
, numpy.testing
tiene el método assert_array_almost_equal
.
Esto funciona para los objetos array_like
a una array_like
, por lo que está bien para matrices, listas y tuplas de flotadores, pero no funciona para conjuntos y diccionarios.
La documentación está here .
Un enfoque alternativo es convertir sus datos en una forma comparable, por ejemplo, convirtiendo cada flotante en una cadena con una precisión fija.
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()}
Entonces tú puedes:
self.assertEquals(comparable(value1), comparable(value2))
Así es como he implementado una función genérica is_almost_equal(first, second)
:
Primero, duplique los objetos que necesita comparar ( first
y second
), pero no haga una copia exacta: corte los dígitos decimales insignificantes de cualquier flotador que encuentre dentro del objeto.
Ahora que tiene copias de la first
y la second
para las que los dígitos decimales insignificantes han desaparecido, simplemente compare la first
y la second
utilizando el operador ==
.
Supongamos que tenemos una función cut_insignificant_digits_recursively(obj, places)
que duplica obj
pero deja solo los places
los dígitos decimales más significativos de cada flotante en el obj
original. Aquí hay una implementación de trabajo de 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
Y aquí hay una implementación de trabajo de 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
El código y sus pruebas de unidad están disponibles aquí: https://github.com/snakile/approximate_comparator . Doy la bienvenida a cualquier mejora y corrección de errores.