Saat menulis kelas khusus seringkali penting untuk memungkinkan kesetaraan melalui operator ==
dan !=
. Dalam Python, ini dimungkinkan dengan mengimplementasikan __eq__
dan __ne__
metode khusus, masing-masing. Cara termudah yang saya temukan untuk melakukan ini adalah metode berikut:
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
Apakah Anda tahu cara yang lebih elegan untuk melakukan ini? Apakah Anda mengetahui adanya kerugian tertentu dalam menggunakan metode perbandingan __dict__
di atas?
Catatan : Sedikit klarifikasi - kapan __eq__
dan __ne__
tidak ditentukan, Anda akan menemukan perilaku ini:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
Artinya, a == b
dievaluasi False
karena benar-benar berjalan a is b
, tes identitas (yaitu, "Apakah a
objek yang sama b
?").
Saat __eq__
dan __ne__
ditentukan, Anda akan menemukan perilaku ini (yang kami kejar):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
sumber
is
operator untuk membedakan identitas objek dari perbandingan nilai.Jawaban:
Pertimbangkan masalah sederhana ini:
Jadi, Python secara default menggunakan pengidentifikasi objek untuk operasi perbandingan:
Mengganti
__eq__
fungsi tampaknya dapat menyelesaikan masalah:Dalam Python 2 , selalu ingat untuk mengganti
__ne__
fungsi juga, seperti yang dinyatakan oleh dokumentasi :Dalam Python 3 , ini tidak lagi diperlukan, karena dokumentasi menyatakan:
Tapi itu tidak menyelesaikan semua masalah kita. Mari kita tambahkan subkelas:
Catatan: Python 2 memiliki dua jenis kelas:
kelas gaya klasik (atau gaya lama ), yang tidak mewarisi dari
object
dan yang dinyatakan sebagaiclass A:
,class A():
atau diclass A(B):
manaB
kelas gaya klasik;kelas gaya baru , yang mewarisi dari
object
dan yang dinyatakan sebagaiclass A(object)
atau diclass A(B):
manaB
kelas gaya baru. Python 3 hanya memiliki kelas gaya baru yang dinyatakan sebagaiclass A:
,class A(object):
atauclass A(B):
.Untuk kelas gaya klasik, operasi perbandingan selalu memanggil metode operan pertama, sedangkan untuk kelas gaya baru, selalu memanggil metode operan subkelas, terlepas dari urutan operan .
Jadi di sini, jika
Number
kelas gaya klasik:n1 == n3
panggilann1.__eq__
;n3 == n1
panggilann3.__eq__
;n1 != n3
panggilann1.__ne__
;n3 != n1
panggilann3.__ne__
.Dan jika
Number
kelas gaya baru:n1 == n3
dann3 == n1
teleponn3.__eq__
;n1 != n3
dann3 != n1
meneleponn3.__ne__
.Untuk memperbaiki masalah non-komutatif dari
==
dan!=
operator untuk kelas gaya klasik Python 2,__eq__
dan__ne__
metode harus mengembalikanNotImplemented
nilai ketika jenis operan tidak didukung. The dokumentasi mendefinisikanNotImplemented
nilai sebagai:Dalam hal ini operator mendelegasikan operasi perbandingan dengan metode yang direfleksikan dari operan lain . The dokumentasi mendefinisikan tercermin metode sebagai:
Hasilnya terlihat seperti ini:
Mengembalikan
NotImplemented
nilai bukanFalse
merupakan hal yang benar untuk dilakukan bahkan untuk kelas gaya baru jika komutatif dari==
dan!=
operator yang diinginkan ketika operan dari jenis yang tidak terkait (tidak ada warisan).Apakah kita sudah sampai? Tidak terlalu. Berapa nomor unik yang kita miliki?
Set menggunakan hash objek, dan secara default Python mengembalikan hash dari pengenal objek. Mari kita coba menimpanya:
Hasil akhirnya terlihat seperti ini (saya menambahkan beberapa pernyataan di akhir untuk validasi):
sumber
hash(tuple(sorted(self.__dict__.items())))
tidak akan berfungsi jika ada objek yang tidak hash di antara nilai-nilai dariself.__dict__
(yaitu, jika salah satu atribut objek diatur ke, katakanlah, alist
).__ne__
menggunakan==
alih-alih__eq__
.__ne__
lagi: "Secara default,__ne__()
delegasikan ke__eq__()
dan membalikkan hasilnya kecuali jika ituNotImplemented
". 2. Jika salah satu masih ingin menerapkan__ne__
, implementasi yang lebih generik (yang digunakan oleh Python 3 saya pikir) adalah:x = self.__eq__(other); if x is NotImplemented: return x; else: return not x
. 3. Yang diberikan__eq__
dan__ne__
implementasi bersifat suboptimal:if isinstance(other, type(self)):
memberikan 22__eq__
dan 10__ne__
panggilan, sementaraif isinstance(self, type(other)):
akan memberikan 16__eq__
dan 6__ne__
panggilan.Anda harus berhati-hati dengan warisan:
Periksa jenis lebih ketat, seperti ini:
Selain itu, pendekatan Anda akan bekerja dengan baik, itulah metode khusus yang ada.
sumber
NotImplemented
seperti yang Anda sarankan akan selalu menyebabkansuperclass.__eq__(subclass)
, yang merupakan perilaku yang diinginkan.if other is self
. Ini menghindari perbandingan kamus yang lebih panjang, dan bisa menjadi penghematan besar ketika objek digunakan sebagai kunci kamus.__hash__()
Cara Anda menggambarkan adalah cara saya selalu melakukannya. Karena ini benar-benar generik, Anda selalu dapat memecah fungsi itu menjadi kelas mixin dan mewarisinya di kelas di mana Anda menginginkan fungsi itu.
sumber
other
dari subkelasself.__class__
.__dict__
perbandingan adalah bagaimana jika Anda memiliki atribut yang tidak ingin Anda pertimbangkan dalam definisi kesetaraan Anda (misalnya, misalnya id objek unik, atau metadata seperti cap waktu yang dibuat).Bukan jawaban langsung tetapi tampaknya cukup relevan untuk ditempelkan karena menghemat sedikit kebosanan verbose pada kesempatan. Dipotong langsung dari dokumen ...
functools.total_ordering (cls)
Mengingat kelas mendefinisikan satu atau lebih metode pemesanan perbandingan kaya, dekorator kelas ini memasok sisanya. Ini menyederhanakan upaya yang terlibat dalam menentukan semua operasi perbandingan kaya yang mungkin:
Kelas harus menentukan salah satu dari
__lt__()
,__le__()
,__gt__()
, atau__ge__()
. Selain itu, kelas harus menyediakan__eq__()
metode.Baru di versi 2.7
sumber
Anda tidak harus menimpa keduanya
__eq__
dan__ne__
Anda dapat menimpa hanya__cmp__
tetapi ini akan membuat implikasi pada hasil ==,! ==, <,> dan seterusnya.is
tes untuk identitas objek. Ini berarti ais
akan beradaTrue
dalam kasus ketika a dan b keduanya memegang referensi ke objek yang sama. Dalam python Anda selalu memegang referensi ke objek dalam variabel bukan objek yang sebenarnya, jadi pada dasarnya untuk a adalah benar, objek di dalamnya harus berada di lokasi memori yang sama. Bagaimana dan yang paling penting mengapa Anda harus mengabaikan perilaku ini?Sunting: Saya tidak tahu
__cmp__
telah dihapus dari python 3 jadi hindarilah.sumber
Dari jawaban ini: https://stackoverflow.com/a/30676267/541136 Saya telah menunjukkan itu, sementara itu benar untuk mendefinisikan
__ne__
dalam hal__eq__
- bukankamu harus menggunakan:
sumber
Saya pikir dua istilah yang Anda cari adalah persamaan (==) dan identitas . Sebagai contoh:
sumber
Tes 'is' akan menguji identitas menggunakan fungsi builtin 'id ()' yang pada dasarnya mengembalikan alamat memori objek dan oleh karena itu tidak kelebihan beban.
Namun dalam hal menguji persamaan kelas Anda mungkin ingin sedikit lebih ketat tentang tes Anda dan hanya membandingkan atribut data di kelas Anda:
Kode ini hanya akan membandingkan data anggota yang tidak berfungsi di kelas Anda serta melewatkan apa pun yang bersifat pribadi yang umumnya Anda inginkan. Dalam kasus Plain Old Python Objects saya memiliki kelas dasar yang mengimplementasikan __init__, __str__, __repr__ dan __eq__ sehingga objek POPO saya tidak membawa beban dari semua logika ekstra (dan dalam banyak kasus identik).
sumber
__eq__
akan dinyatakan dalamCommonEqualityMixin
(lihat jawaban lainnya). Saya menemukan ini sangat berguna ketika membandingkan contoh kelas yang berasal dari Base di SQLAlchemy. Untuk tidak membandingkan_sa_instance_state
saya berubahkey.startswith("__")):
menjadikey.startswith("_")):
. Saya juga memiliki beberapa referensi di dalamnya dan jawaban dari Algorias menghasilkan rekursi yang tak ada habisnya. Jadi saya menamai semua referensi kembali dimulai dengan'_'
sehingga mereka juga dilewati selama perbandingan. CATATAN: dalam Python 3.x ubahiteritems()
menjadiitems()
.__dict__
instance tidak memiliki apa pun yang dimulai__
kecuali ditentukan oleh pengguna. Hal-hal seperti__class__
,,__init__
dll. Tidak ada dalam instance__dict__
, melainkan dalam kelasnya '__dict__
. OTOH, atribut pribadi dapat dengan mudah dimulai__
dan mungkin harus digunakan__eq__
. Bisakah Anda mengklarifikasi apa yang sebenarnya Anda coba hindari ketika melewatkan__
atribut -prefixed?Alih-alih menggunakan subclassing / mixin, saya lebih suka menggunakan dekorator kelas generik
Pemakaian:
sumber
Ini menggabungkan komentar pada jawaban Algorias, dan membandingkan objek dengan satu atribut karena saya tidak peduli dengan keseluruhan dikt.
hasattr(other, "id")
pasti benar, tetapi saya tahu itu karena saya meletakkannya di konstruktor.sumber