Если вы имеете дело с одним или несколькими классами, которые вы не можете изменить изнутри, есть универсальные и простые способы сделать это, которые также не зависят от библиотеки, специфичной для diff:
Самый простой, небезопасный метод для очень сложных объектов
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/…