Se hai a che fare con una o più classi che non puoi cambiare dall'interno, ci sono modi generici e semplici per farlo che non dipendono da una libreria specifica per diff:
Metodo più semplice, non sicuro per oggetti molto complessi
pickle.dumps(a) == pickle.dumps(b)
pickle
è una lib di serializzazione molto comune per gli oggetti Python e sarà quindi in grado di serializzare praticamente qualsiasi cosa, davvero. Nel frammento sopra sto confrontando il str
da serializzato a
con quello di b
. A differenza del metodo successivo, questo ha il vantaggio di controllare anche le classi personalizzate.
Il problema maggiore: a causa di specifici metodi di ordinamento e di codifica [de / en], pickle
potrebbe non produrre lo stesso risultato per oggetti uguali , specialmente quando si tratta di più complessi (ad esempio elenchi di istanze di classe personalizzate nidificate) come spesso si trovano in alcune librerie di terze parti. Per questi casi, consiglierei un approccio diverso:
Metodo completo, sicuro per qualsiasi oggetto
Potresti scrivere una riflessione ricorsiva che ti darà oggetti serializzabili e quindi confrontare i risultati
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:])
Ora non importa quali siano i tuoi oggetti, l'eguaglianza profonda è garantita per funzionare
>>> 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
Anche il numero di elementi comparabili non ha importanza
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Il mio caso d'uso è stato quello di verificare la profonda uguaglianza tra una serie diversificata di modelli di Machine Learning già addestrati all'interno dei test BDD. I modelli appartenevano a una serie diversificata di librerie di terze parti. Certamente implementando__eq__
come altre risposte qui suggerisce che non era un'opzione per me.
Coprendo tutte le basi
Potresti trovarti in uno scenario in cui una o più classi personalizzate confrontate non hanno __dict__
un'implementazione . Questo non è comune con qualsiasi mezzo, ma è il caso di un sottotipo all'interno classificatore Foresta caso di sklearn: <type 'sklearn.tree._tree.Tree'>
. Trattare queste situazioni caso per caso - ad esempio , in particolare , ho deciso di sostituire il contenuto del tipo afflitto con il contenuto di un metodo che mi fornisce informazioni rappresentative sull'istanza (in questo caso, il __getstate__
metodo). Per tale motivo, la penultima riga è base_typed
diventata
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Modifica: per motivi di organizzazione, ho sostituito le ultime due righe di base_typed
con return dict_from(obj)
, e ho implementato una riflessione davvero generica per accogliere librerie più oscure (ti sto guardando, 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()}
Ricorda che nessuno dei metodi sopra riportati produce True
oggetti diversi con le stesse coppie chiave-valore ma ordini chiave / valore diversi, come in
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Ma se lo desideri, puoi comunque utilizzare il sorted
metodo integrato di Python in anticipo.
return NotImplemented
(invece di aumentareNotImplementedError
). Questo argomento è coperto qui: stackoverflow.com/questions/878943/...