Jika Anda berurusan dengan satu atau beberapa kelas yang tidak dapat Anda ubah dari dalam, ada cara umum dan sederhana untuk melakukan ini yang juga tidak bergantung pada pustaka khusus:
Metode objek termudah, tidak aman untuk objek yang sangat kompleks
pickle.dumps(a) == pickle.dumps(b)
pickle
adalah lib serialisasi yang sangat umum untuk objek Python, dan dengan demikian akan dapat membuat serialisasi apa saja, sungguh. Dalam cuplikan di atas saya membandingkan str
dari serial a
dengan yang dari b
. Berbeda dengan metode selanjutnya, yang satu ini memiliki keuntungan juga mengetik kelas kustom.
Kerumitan terbesar: karena pemesanan khusus dan metode pengkodean, pickle
tidak dapat menghasilkan hasil yang sama untuk objek yang sama , khususnya ketika berhadapan dengan yang lebih kompleks (mis. Daftar instance kelas kustom bersarang) seperti Anda akan sering menemukan di beberapa lib pihak ketiga. Untuk kasus-kasus itu, saya akan merekomendasikan pendekatan yang berbeda:
Metode objek yang menyeluruh, aman untuk apa saja
Anda bisa menulis refleksi rekursif yang akan memberi Anda objek serial, dan kemudian membandingkan hasilnya
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:])
Sekarang tidak masalah apa objek Anda, kesetaraan yang dalam dijamin untuk bekerja
>>> 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
Jumlah yang sebanding tidak masalah juga
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
Kasus penggunaan saya untuk ini adalah memeriksa kesetaraan yang mendalam di antara beragam model Pembelajaran Mesin yang sudah terlatih dalam tes BDD. Model-model tersebut milik beragam set lib pihak ketiga. Tentunya menerapkan __eq__
seperti jawaban lain di sini menyarankan bukan pilihan bagi saya.
Menutupi semua pangkalan
Anda mungkin berada dalam skenario di mana satu atau beberapa kelas khusus yang dibandingkan tidak memiliki __dict__
implementasi . Itu tidak umum dengan cara apapun, tapi itu adalah kasus subtipe dalam classifier Acak Hutan sklearn ini: <type 'sklearn.tree._tree.Tree'>
. Perlakukan situasi ini dalam kasus per kasus - misalnya secara spesifik , saya memutuskan untuk mengganti konten dari jenis yang diderita dengan konten dari metode yang memberi saya informasi yang representatif pada contoh (dalam kasus ini, __getstate__
metode). Untuk itu, baris kedua ke terakhir base_typed
menjadi
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Sunting: demi organisasi, saya mengganti dua baris terakhir base_typed
dengan return dict_from(obj)
, dan menerapkan refleksi yang benar-benar umum untuk mengakomodasi lebih banyak lib yang tidak jelas (Saya melihat Anda, 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()}
Jangan pedulikan metode di atas menghasilkan True
objek yang berbeda dengan pasangan nilai kunci yang sama tetapi pesanan kunci / nilai yang berbeda, seperti pada
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
Tetapi jika Anda mau, Anda bisa menggunakan sorted
metode bawaan Python sebelumnya.
return NotImplemented
(bukannya meningkatkanNotImplementedError
). Topik itu dibahas di sini: stackoverflow.com/questions/878943/…