Se você está lidando com uma ou mais classes que não podem ser alteradas internamente, existem maneiras simples e genéricas de fazer isso que também não dependem de uma biblioteca específica do diff:
Método mais fácil e inseguro para objetos muito complexos
pickle.dumps(a) == pickle.dumps(b)
pickle
é uma biblioteca de serialização muito comum para objetos Python e, portanto, poderá serializar praticamente qualquer coisa. No trecho acima, estou comparando o str
de serializado a
com o de b
. Ao contrário do próximo método, este tem a vantagem de também verificar classes personalizadas.
O maior problema: devido a métodos específicos de ordenação e [de / en], pickle
pode não produzir o mesmo resultado para objetos iguais , especialmente ao lidar com objetos mais complexos (por exemplo, listas de instâncias de classes personalizadas aninhadas), como você frequentemente encontrará em algumas bibliotecas de terceiros. Para esses casos, eu recomendaria uma abordagem diferente:
Método completo e seguro para qualquer objeto
Você pode escrever uma reflexão recursiva que fornecerá objetos serializáveis e comparar os resultados
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:])
Agora, não importa quais são seus objetos, é garantida que a profunda igualdade funcione
>>> 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
O número de comparáveis também não importa
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Meu caso de uso para isso foi verificar a profunda igualdade entre um conjunto diversificado de modelos de Machine Learning já treinados nos testes do BDD. Os modelos pertenciam a um conjunto diversificado de bibliotecas de terceiros. Certamente implementar __eq__
como outras respostas aqui sugerem que não era uma opção para mim.
Cobrindo todas as bases
Você pode estar em um cenário em que uma ou mais das classes personalizadas que estão sendo comparadas não possuem uma __dict__
implementação . Isso não é comum, por qualquer meio, mas é o caso de um subtipo dentro classificador aleatória Floresta do sklearn: <type 'sklearn.tree._tree.Tree'>
. Trate essas situações caso a caso - por exemplo , especificamente , decidi substituir o conteúdo do tipo afetado pelo conteúdo de um método que me fornece informações representativas sobre a instância (nesse caso, o __getstate__
método). Para isso, a penúltima linha da lista base_typed
tornou-se
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Edit: por uma questão de organização, substituí as duas últimas linhas base_typed
por return dict_from(obj)
e implementei uma reflexão realmente genérica para acomodar bibliotecas mais obscuras (estou olhando para você, 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()}
Lembre-se de que nenhum dos métodos acima se aplica True
a objetos diferentes com os mesmos pares de chave-valor, mas com diferentes ordens de chave / valor, como em
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Mas se você quiser, poderá usar o sorted
método interno do Python de qualquer maneira.
return NotImplemented
(em vez de aumentarNotImplementedError
). Esse tópico é abordado aqui: stackoverflow.com/questions/878943/…