พิจารณาปัญหาง่ายๆนี้:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
ดังนั้น Python โดยค่าเริ่มต้นใช้ตัวระบุวัตถุสำหรับการดำเนินการเปรียบเทียบ:
id(n1) # 140400634555856
id(n2) # 140400634555920
การเอาชนะ__eq__ฟังก์ชั่นดูเหมือนว่าจะแก้ปัญหา:
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
ในPython 2อย่าลืมแทนที่__ne__ฟังก์ชั่นเช่นเดียวกับสถานะเอกสาร :
ไม่มีความสัมพันธ์โดยนัยระหว่างโอเปอเรเตอร์การเปรียบเทียบ ความจริงx==yไม่ได้หมายความว่าx!=yเป็นเรื่องผิด ดังนั้นเมื่อกำหนด__eq__()หนึ่งควรกำหนด__ne__()เพื่อให้ผู้ประกอบการจะทำงานตามที่คาดไว้
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
ในPython 3สิ่งนี้ไม่จำเป็นอีกต่อไปเนื่องจากสถานะเอกสารประกอบ :
โดยค่าเริ่มต้น__ne__()ได้รับมอบหมายให้และตีความผลเว้นแต่เป็น__eq__() NotImplementedไม่มีความสัมพันธ์โดยนัยอื่น ๆ ของผู้ประกอบการเปรียบเทียบเป็นตัวอย่างเช่นความจริงของไม่ได้หมายความถึง(x<y or x==y)x<=y
แต่นั่นไม่ได้แก้ปัญหาทั้งหมดของเรา ลองเพิ่มคลาสย่อย:
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
หมายเหตุ: Python 2 มีคลาสสองประเภท:
สไตล์คลาสสิก (หรือแบบเก่า ) ชั้นเรียนที่ไม่ได้รับมรดกจากobjectและที่ได้รับการประกาศให้เป็นclass A:,class A():หรือclass A(B):ที่Bเป็นชั้นสไตล์คลาสสิก;
คลาสสไตล์ใหม่ที่สืบทอดจากobjectและที่ถูกประกาศเป็นclass A(object)หรือclass A(B):ตำแหน่งBคลาสสไตล์ใหม่ งูหลาม 3 มีเพียงชั้นเรียนแบบใหม่ที่ได้รับการประกาศให้เป็นclass A:,หรือclass A(object):class A(B):
สำหรับการเรียนสไตล์คลาสสิกการดำเนินการเปรียบเทียบเสมอเรียกวิธีการของตัวถูกดำเนินการครั้งแรกในขณะที่สำหรับการเรียนแบบใหม่ก็มักจะเรียกวิธีการของตัวถูกดำเนินการ subclass ที่ไม่คำนึงถึงคำสั่งของตัวถูกดำเนินการที่
ดังนั้นที่นี่ถ้าNumberเป็นคลาสคลาสสิก:
n1 == n3โทรn1.__eq__;
n3 == n1โทรn3.__eq__;
n1 != n3โทรn1.__ne__;
n3 != n1n3.__ne__โทร
และถ้าNumberเป็นคลาสแบบใหม่:
- ทั้ง
n1 == n3และn3 == n1โทรn3.__eq__;
- ทั้งสอง
n1 != n3และการโทรn3 != n1n3.__ne__
ในการแก้ไขปัญหา non-commutativity ของ==และ!=ตัวดำเนินการสำหรับคลาสคลาสสิก Python 2, __eq__และ__ne__เมธอดควรส่งคืนNotImplementedค่าเมื่อไม่สนับสนุนชนิดตัวถูกดำเนินการ เอกสารกำหนดNotImplementedค่าเป็น:
วิธีการตัวเลขและวิธีการเปรียบเทียบที่หลากหลายอาจส่งคืนค่านี้หากไม่ได้ใช้การดำเนินการสำหรับตัวถูกดำเนินการที่ให้ไว้ (ล่ามจะลองใช้การสะท้อนกลับหรือทางเลือกอื่น ๆ ขึ้นอยู่กับผู้ปฏิบัติงาน) ค่าความจริงของมันเป็นจริง
ในกรณีนี้ผู้ประกอบการมอบหมายการดำเนินการเปรียบเทียบกับวิธีการสะท้อนของตัวถูกดำเนินการอื่น เอกสารกำหนดสะท้อนให้เห็นถึงวิธีการดังนี้
ไม่มีวิธีการโต้แย้งในรุ่นที่สลับกัน (จะใช้เมื่ออาร์กิวเมนต์ซ้ายไม่สนับสนุนการดำเนินการ แต่มีอาร์กิวเมนต์ที่ถูกต้อง) ค่อนข้าง__lt__()และ__gt__()เป็นภาพสะท้อนของกันและกัน__le__()และ__ge__()มีการสะท้อนของกันและกันและ
__eq__()และ__ne__()เป็นภาพสะท้อนของตัวเอง
ผลลัพธ์จะเป็นดังนี้:
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
การกลับไปยังNotImplementedค่าแทนที่จะFalseเป็นสิ่งที่ถูกต้องที่จะทำแม้กระทั่งสำหรับการเรียนแบบใหม่ถ้าcommutativityของ==และ!=ผู้ประกอบการที่เป็นที่ต้องการเมื่อถูกดำเนินการเป็นประเภทที่ไม่เกี่ยวข้องกัน (มรดก)
พวกเราอยู่ที่นั่นหรือยัง ไม่มาก เรามีตัวเลขที่ไม่ซ้ำกันจำนวนเท่าใด
len(set([n1, n2, n3])) # 3 -- oops
ชุดใช้การแฮชของวัตถุและโดยค่าเริ่มต้น Python จะส่งคืนค่าแฮชของตัวระบุของวัตถุ ลองแทนที่มัน:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
ผลลัพธ์สุดท้ายจะมีลักษณะดังนี้ (ฉันได้เพิ่มการยืนยันบางอย่างในตอนท้ายสำหรับการตรวจสอบ):
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โอเปอเรเตอร์เพื่อแยกเอกลักษณ์ของออบเจกต์จากการเปรียบเทียบค่า