Pertimbangkan masalah sederhana ini:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
Jadi, Python secara default menggunakan pengidentifikasi objek untuk operasi perbandingan:
id(n1) # 140400634555856
id(n2) # 140400634555920
Mengganti __eq__
fungsi tampaknya dapat menyelesaikan masalah:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
Dalam Python 2 , selalu ingat untuk mengganti __ne__
fungsi juga, seperti yang dinyatakan oleh dokumentasi :
Tidak ada hubungan yang tersirat di antara operator pembanding. Kebenaran x==y
tidak menyiratkan bahwa x!=y
itu salah. Dengan demikian, ketika mendefinisikan __eq__()
, kita juga harus mendefinisikan __ne__()
sehingga operator akan berperilaku seperti yang diharapkan.
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
Dalam Python 3 , ini tidak lagi diperlukan, karena dokumentasi menyatakan:
Secara default, __ne__()
delegasikan ke __eq__()
dan membalikkan hasilnya kecuali jika itu benar NotImplemented
. Tidak ada hubungan tersirat lainnya di antara operator pembanding, misalnya, kebenaran (x<y or x==y)
tidak menyiratkan x<=y
.
Tapi itu tidak menyelesaikan semua masalah kita. Mari kita tambahkan subkelas:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
Catatan: Python 2 memiliki dua jenis kelas:
kelas gaya klasik (atau gaya lama ), yang tidak mewarisi dariobject
dan yang dinyatakan sebagaiclass A:
,class A():
atau diclass A(B):
manaB
kelas gaya klasik;
kelas gaya baru , yang mewarisi dariobject
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
panggilan n1.__eq__
;
n3 == n1
panggilan n3.__eq__
;
n1 != n3
panggilan n1.__ne__
;
n3 != n1
panggilan n3.__ne__
.
Dan jika Number
kelas gaya baru:
- keduanya
n1 == n3
dan n3 == n1
telepon n3.__eq__
;
- keduanya
n1 != n3
dan n3 != n1
menelepon n3.__ne__
.
Untuk memperbaiki masalah non-komutatif dari ==
dan !=
operator untuk kelas gaya klasik Python 2, __eq__
dan __ne__
metode harus mengembalikan NotImplemented
nilai ketika jenis operan tidak didukung. The dokumentasi mendefinisikan NotImplemented
nilai sebagai:
Metode numerik dan metode perbandingan kaya dapat mengembalikan nilai ini jika mereka tidak mengimplementasikan operasi untuk operan yang disediakan. (Penerjemah kemudian akan mencoba operasi yang direfleksikan, atau beberapa fallback lainnya, tergantung pada operator.) Nilai kebenarannya benar.
Dalam hal ini operator mendelegasikan operasi perbandingan dengan metode yang direfleksikan dari operan lain . The dokumentasi mendefinisikan tercermin metode sebagai:
Tidak ada versi argumen bertukar metode ini (untuk digunakan ketika argumen kiri tidak mendukung operasi tetapi argumen yang benar tidak); lebih, __lt__()
dan __gt__()
merupakan cerminan satu sama lain, __le__()
dan __ge__()
merupakan cerminan satu sama lain, dan
__eq__()
dan __ne__()
merupakan cerminan mereka sendiri.
Hasilnya terlihat seperti ini:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
Mengembalikan NotImplemented
nilai bukan False
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?
len(set([n1, n2, n3])) # 3 -- oops
Set menggunakan hash objek, dan secara default Python mengembalikan hash dari pengenal objek. Mari kita coba menimpanya:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
Hasil akhirnya terlihat seperti ini (saya menambahkan beberapa pernyataan di akhir untuk validasi):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2
is
operator untuk membedakan identitas objek dari perbandingan nilai.