Wenn Sie es mit einer oder mehreren Klassen zu tun haben, die Sie von innen nicht ändern können , gibt es allgemeine und einfache Möglichkeiten, die auch nicht von einer diff-spezifischen Bibliothek abhängen:
Einfachste, für sehr komplexe Objekte unsichere Methode
pickle.dumps(a) == pickle.dumps(b)
pickle
ist eine sehr verbreitete Serialisierungsbibliothek für Python-Objekte und kann daher so ziemlich alles serialisieren. Im obigen Snippet vergleiche ich das str
von serialisiert a
mit dem vonb
. Im Gegensatz zur nächsten Methode hat diese den Vorteil, dass auch benutzerdefinierte Klassen auf Typprüfung überprüft werden können.
Der größte Aufwand: Aufgrund spezifischer Ordnungs- und [de / en] -Codierungsmethoden wird pickle
möglicherweise nicht das gleiche Ergebnis für gleiche Objekte erzielt , insbesondere bei komplexeren Objekten (z. B. Listen verschachtelter Instanzen benutzerdefinierter Klassen), wie Sie sie häufig finden in einigen Bibliotheken von Drittanbietern. Für diese Fälle würde ich einen anderen Ansatz empfehlen:
Gründliche, für jedes Objekt sichere Methode
Sie können eine rekursive Reflexion schreiben, die Ihnen serialisierbare Objekte liefert, und dann die Ergebnisse vergleichen
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:])
Jetzt spielt es keine Rolle, was Ihre Objekte sind, tiefe Gleichheit ist sicher zu funktionieren
>>> 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
Die Anzahl der Vergleiche spielt ebenfalls keine Rolle
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Mein Anwendungsfall hierfür war die Überprüfung der tiefen Gleichheit zwischen einer Vielzahl bereits trainierter Modelle für maschinelles Lernen in BDD-Tests. Die Modelle gehörten zu einer Vielzahl von Bibliotheken von Drittanbietern. __eq__
Wie andere Antworten hier zu implementieren , war für mich sicherlich keine Option.
Bedeckt alle Basen
Möglicherweise befinden Sie sich in einem Szenario, in dem eine oder mehrere der verglichenen benutzerdefinierten Klassen keine __dict__
Implementierung haben . Das ist keineswegs üblich, aber es handelt sich um einen Subtyp innerhalb des Random Forest-Klassifikators von sklearn : <type 'sklearn.tree._tree.Tree'>
. Behandeln Sie diese Situationen in einem Einzelfall - zum Beispiel speziell , ich den Inhalt des betroffenen Typs mit dem Inhalt einer Methode zu ersetzen , entschieden , die mir repräsentative Informationen über die Instanz (in diesem Fall das gibt __getstate__
Methode). Für eine solche, in der zweiten bis letzten Zeile base_typed
wurde
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Bearbeiten: Aus Gründen der Organisation habe ich die letzten beiden Zeilen base_typed
durch ersetzt return dict_from(obj)
und eine wirklich allgemeine Reflexion implementiert, um dunkelere Bibliotheken aufzunehmen (ich sehe dich an, 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()}
Beachten Sie, dass keine der oben genannten Methoden True
für verschiedene Objekte mit denselben Schlüssel-Wert-Paaren, aber unterschiedlichen Schlüssel / Wert-Reihenfolgen wie in ergibt
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Wenn Sie dies jedoch möchten, können Sie die integrierte sorted
Methode von Python ohnehin vorher verwenden.
return NotImplemented
(anstatt zu erhöhenNotImplementedError
). Dieses Thema wird hier behandelt: stackoverflow.com/questions/878943/…