Si vous avez affaire à une ou plusieurs classes que vous ne pouvez pas modifier de l'intérieur, il existe des moyens génériques et simples de le faire qui ne dépendent pas non plus d'une bibliothèque spécifique aux différences:
Méthode la plus simple et peu sûre pour les objets très complexes
pickle.dumps(a) == pickle.dumps(b)
pickle
est une bibliothèque de sérialisation très courante pour les objets Python, et sera donc capable de sérialiser à peu près n'importe quoi, vraiment. Dans l'extrait ci-dessus, je compare le str
de sérialisé a
à celui de b
. Contrairement à la méthode suivante, celle-ci présente également l'avantage de vérifier les classes personnalisées.
Le plus gros problème: en raison de méthodes de classement et de codage [de / en] spécifiques, il se pickle
peut que le résultat ne soit pas le même pour des objets égaux , en particulier lorsqu'il s'agit d' plus complexes (par exemple, des listes d'instances de classe personnalisée imbriquées) comme vous en trouverez fréquemment dans certaines bibliothèques tierces. Pour ces cas, je recommanderais une approche différente:
Méthode complète et sûre pour tout objet
Vous pouvez écrire une réflexion récursive qui vous donnera des objets sérialisables, puis comparer les résultats
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:])
Peu importe ce que sont vos objets, une égalité profonde est assurée pour fonctionner
>>> 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
Peu importe le nombre de comparables
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Mon cas d'utilisation était de vérifier une profonde égalité parmi un ensemble diversifié de modèles d'apprentissage automatique déjà formés dans les tests BDD. Les modèles appartenaient à un ensemble diversifié de bibliothèques tierces. La mise en œuvre __eq__
comme les autres réponses suggèrent certainement que ce n'était pas une option pour moi.
Couvrant toutes les bases
Vous pouvez être dans un scénario où une ou plusieurs des classes personnalisées comparées n'ont pas d' __dict__
implémentation . Ce n'est pas commun par tous les moyens, mais il est le cas d'un sous - type au sein du classificateur Forêt aléatoire de sklearn: <type 'sklearn.tree._tree.Tree'>
. Traitez ces situations au cas par cas - par exemple, spécifiquement , j'ai décidé de remplacer le contenu du type affecté par le contenu d'une méthode qui me donne des informations représentatives sur l'instance (dans ce cas, la __getstate__
méthode). Pour tel, l'avant-dernier rang base_typed
est devenu
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Edit: pour des raisons d'organisation, j'ai remplacé les deux dernières lignes de base_typed
with return dict_from(obj)
, et implémenté une réflexion vraiment générique pour accueillir des bibliothèques plus obscures (je vous regarde, 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()}
N'oubliez pas qu'aucune des méthodes ci-dessus ne donne True
pour différents objets avec les mêmes paires clé-valeur mais des ordres clé / valeur différents, comme dans
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Mais si vous le souhaitez, vous pouvez de toute façon utiliser la sorted
méthode intégrée de Python au préalable.
return NotImplemented
(au lieu d'éleverNotImplementedError
). Ce sujet est couvert ici: stackoverflow.com/questions/878943/…