如果您正在处理一个或多个您无法从内部更改的类,则有一些通用且简单的方法可以做到这一点,它们也不依赖于特定于 diff图书馆:
最简单但对非常复杂的对象不安全的方法
pickle.dumps(a) == pickle.dumps(b)
pickle 是一个非常常见的 Python 对象序列化库,因此可以序列化几乎任何东西,真的。在上面的 sn-p 中,我将来自序列化的a 的str 与来自b 的一个进行比较。与下一种方法不同,这种方法的优点是还可以对自定义类进行类型检查。
最大的麻烦:由于特定的排序和 [de/en] 编码方法,pickle may not yield the same result for equal objects,尤其是在处理更复杂的(例如嵌套自定义类实例的列表)时,就像您经常在第三个中发现的那样-派对库。对于这些情况,我建议采用不同的方法:
彻底、对任何对象都安全的方法
您可以编写一个递归反射,为您提供可序列化的对象,然后比较结果
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__ 实现。这无论如何都不常见,但 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__()
编辑:为了组织起见,我用return dict_from(obj) 替换了上面可怕的oneliner。在这里,dict_from 是一个非常通用的反射,用于容纳更多晦涩的库(我在看着你,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):
# Use standard dict representation when available
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
但如果你愿意,无论如何你都可以事先使用 Python 的内置 sorted 方法。