Якщо ви маєте справу з одним або декількома класами, які ви не можете змінити зсередини, існують загальні та прості способи зробити це, які також не залежать від розробленої бібліотеки, що відрізняється:
Найпростіший, небезпечний для дуже складних об'єктів метод
pickle.dumps(a) == pickle.dumps(b)
pickle
є дуже поширеною ланкою серіалізації для об'єктів Python, і таким чином зможе серіалізувати майже все, що насправді. У наведеному вище фрагменті я порівнюю str
серіалізований a
з тим, від якого b
. На відміну від наступного методу, цей має перевагу також у тому, щоб перевірити власні класи.
Найбільші клопоти: завдяки специфічним методам упорядкування та [de / en] методів кодування pickle
може не дати однакового результату для рівних об'єктів , особливо при роботі зі складнішими (наприклад, списками вкладених екземплярів спеціального класу), як ви часто зустрічаєте в деяких сторонніх губах. У цих випадках я рекомендую інший підхід:
Ретельний, безпечний для будь-якого об'єкта метод
Ви можете написати рекурсивне відображення, яке дасть вам серіалізаційні об'єкти, а потім порівняти результати
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:])
Тепер не має значення, які у вас об’єкти, глибока рівність гарантована
>>> 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
Кількість порівнянних також не має значення
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Моїм прикладом для цього була перевірка глибокої рівності серед різноманітного набору вже навчених моделей машинного навчання всередині тестів BDD. Моделі належали до різноманітного набору сторонніх ліфтів. Безумовно, реалізація, __eq__
як і інші відповіді тут, передбачає, що для мене це не було варіантом.
Покриває всі основи
Ви можете знаходитись у сценарії, коли один або кілька порівнюваних користувацьких класів не мають __dict__
реалізації . Це не часто будь-якими засобами, але це той випадок підтипу в Random Forest класифікатором sklearn в: <type 'sklearn.tree._tree.Tree'>
. Ставтесь до цих ситуацій у кожному конкретному випадку - наприклад, конкретно , я вирішив замінити вміст ураженого типу змістом методу, який дає мені репрезентативну інформацію про примірник (в даному випадку - __getstate__
метод). Для таких, другий до останнього рядка в base_typed
стали
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Edit: заради організації, я замінив останні два рядки base_typed
з return dict_from(obj)
, і реалізував дійсно родове відображення , щоб вмістити більше неясні LIBS (я дивлюся на вас, 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()}
Не майте на увазі, що жоден із перерахованих вище методів не поступається True
для різних об'єктів з однаковими парами ключ-значення, але різні замовлення ключ / значення, як у
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Але якщо ви хочете, щоб ви могли sorted
заздалегідь скористатися вбудованим методом Python .
return NotImplemented
(замість підвищенняNotImplementedError
). Ця тема покрита тут: stackoverflow.com/questions/878943 / ...