Jeśli masz do czynienia z jedną lub kilkoma klasami, których nie możesz zmienić od wewnątrz, istnieją ogólne i proste sposoby, aby to zrobić, które również nie zależą od biblioteki specyficznej dla różnic:
Najłatwiejsza, najbardziej niebezpieczna metoda dla bardzo złożonych obiektów
pickle.dumps(a) == pickle.dumps(b)
pickle
jest bardzo popularną biblioteką serializacji dla obiektów Pythona i dlatego będzie w stanie serializować praktycznie wszystko. W powyższym fragmencie porównuję numer str
seryjny a
z kodem z b
. W przeciwieństwie do kolejnej metody, ta ma tę zaletę, że sprawdza także niestandardowe klasy.
Największy problem: ze względu na specyficzne metody porządkowania i kodowania [de / en] pickle
mogą nie dawać tego samego wyniku dla równych obiektów , szczególnie w przypadku bardziej złożonych (np. List zagnieżdżonych instancji klasy niestandardowej), jak to często można znaleźć w niektórych bibliotekach stron trzecich. W takich przypadkach zaleciłbym inne podejście:
Dokładna, bezpieczna dla dowolnego obiektu metoda
Możesz napisać rekurencyjną refleksję, która da ci obiekty do serializacji, a następnie porównać wyniki
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:])
Teraz nie ma znaczenia, jakie są twoje obiekty, zapewniona jest głęboka równość
>>> 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
Liczba porównywalnych elementów również nie ma znaczenia
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Moim przypadkiem użycia było sprawdzenie głębokiej równości między różnorodnym zestawem już przeszkolonych modeli uczenia maszynowego w testach BDD. Modele należały do różnorodnego zestawu bibliotek stron trzecich. Z pewnością zastosowanie __eq__
innych odpowiedzi tutaj sugeruje, że nie było dla mnie opcją.
Obejmuje wszystkie bazy
Możesz znajdować się w scenariuszu, w którym co najmniej jedna z porównywanych klas niestandardowych nie ma __dict__
implementacji . To nie jest powszechne w jakikolwiek sposób, ale jest to przypadek podtypu w sklearn Losowa Lasu klasyfikatora: <type 'sklearn.tree._tree.Tree'>
. Traktować te sytuacje, w każdym indywidualnym przypadku - np konkretnie , postanowiłem zastąpić zawartość strapionych typu z treścią metody, która daje mi informacje na reprezentatywnej instancji (w tym przypadku __getstate__
metoda). W tym celu base_typed
stał się drugi do ostatniego rzędu
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Edit: dla dobra organizacji, wymieniłem dwa ostatnie wiersze base_typed
z return dict_from(obj)
, i wdrożył naprawdę rodzajowy refleksji aby pomieścić więcej niejasnych bibliotekami (Patrzę na ciebie, 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()}
Nieważne, że żadna z powyższych metod nie daje wyników True
dla różnych obiektów o tych samych parach klucz-wartość, ale o różnych porządkach klucz / wartość, jak w
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Ale jeśli chcesz, możesz wcześniej skorzystać z wbudowanej sorted
metody Pythona .
return NotImplemented
(zamiast podbijaniaNotImplementedError
). Temat ten jest omawiany tutaj: stackoverflow.com/questions/878943/…