Si está tratando con una o más clases que no puede cambiar desde adentro, existen formas genéricas y simples de hacerlo que tampoco dependen de una biblioteca específica de diferencia:
Método más fácil, inseguro para objetos muy complejos
pickle.dumps(a) == pickle.dumps(b)
pickle
es una lib de serialización muy común para los objetos de Python y, por lo tanto, podrá serializar prácticamente cualquier cosa. En el fragmento anterior, comparo el str
de serializado a
con el de b
. A diferencia del siguiente método, este tiene la ventaja de también escribir clases personalizadas de verificación.
La mayor molestia: debido a la ordenación específica y a los métodos de codificación [de / en], pickle
puede que no se obtenga el mismo resultado para objetos iguales , especialmente cuando se trata de más complejos (por ejemplo, listas de instancias de clase personalizada anidadas) como encontrará con frecuencia en algunas librerías de terceros. Para esos casos, recomendaría un enfoque diferente:
Método completo, seguro para cualquier objeto
Podría escribir una reflexión recursiva que le dará objetos serializables y luego comparar resultados
from collections.abc import Iterable
BASE_TYPES = [str, int, float, bool, type(None)]
def base_typed(obj):
"""Recursive reflection method to convert any object property into a comparable form.
"""
T = type(obj)
from_numpy = T.__module__ == 'numpy'
if T in BASE_TYPES or callable(obj) or (from_numpy and not isinstance(T, Iterable)):
return obj
if isinstance(obj, Iterable):
base_items = [base_typed(item) for item in obj]
return base_items if from_numpy else T(base_items)
d = obj if T is dict else obj.__dict__
return {k: base_typed(v) for k, v in d.items()}
def deep_equals(*args):
return all(base_typed(args[0]) == base_typed(other) for other in args[1:])
Ahora no importa cuáles sean sus objetos, se garantiza que la igualdad profunda funcione
>>> from sklearn.ensemble import RandomForestClassifier
>>>
>>> a = RandomForestClassifier(max_depth=2, random_state=42)
>>> b = RandomForestClassifier(max_depth=2, random_state=42)
>>>
>>> deep_equals(a, b)
True
El número de comparables tampoco importa
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Mi caso de uso para esto fue verificar la profunda igualdad entre un conjunto diverso de modelos de Machine Learning ya entrenados dentro de las pruebas BDD. Los modelos pertenecían a un conjunto diverso de librerías de terceros. Ciertamente, implementar __eq__
como otras respuestas aquí sugieren que no era una opción para mí.
Cubriendo todas las bases
Puede encontrarse en un escenario en el que una o más de las clases personalizadas que se comparan no tienen una __dict__
implementación . Eso no es común, por cualquier medio, pero es el caso de un subtipo dentro clasificador Bosque aleatoria de sklearn: <type 'sklearn.tree._tree.Tree'>
. Trate estas situaciones caso por caso; por ejemplo , específicamente , decidí reemplazar el contenido del tipo afectado con el contenido de un método que me proporciona información representativa sobre la instancia (en este caso, el __getstate__
método). Para tal, la penúltima fila en se base_typed
convirtió en
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Editar: por el bien de la organización, reemplacé las dos últimas líneas base_typed
con return dict_from(obj)
e implementé una reflexión realmente genérica para acomodar bibliotecas más oscuras (te estoy mirando, Doc2Vec)
def isproperty(prop, obj):
return not callable(getattr(obj, prop)) and not prop.startswith('_')
def dict_from(obj):
"""Converts dict-like objects into dicts
"""
if isinstance(obj, dict):
# Dict and subtypes are directly converted
d = dict(obj)
elif '__dict__' in dir(obj):
d = obj.__dict__
elif str(type(obj)) == 'sklearn.tree._tree.Tree':
# Replaces sklearn trees with their state metadata
d = obj.__getstate__()
else:
# Extract non-callable, non-private attributes with reflection
kv = [(p, getattr(obj, p)) for p in dir(obj) if isproperty(p, obj)]
d = {k: v for k, v in kv}
return {k: base_typed(v) for k, v in d.items()}
Tenga en cuenta que ninguno de los métodos anteriores rinde True
para diferentes objetos con los mismos pares clave-valor pero diferentes órdenes clave / valor, como en
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Pero si lo desea, puede utilizar de sorted
antemano el método incorporado de Python .
return NotImplemented
(en lugar de aumentarNotImplementedError
). Ese tema está cubierto aquí: stackoverflow.com/questions/878943/…