พิจารณาปัญหาง่ายๆนี้:
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 != n1
n3.__ne__
โทร
และถ้าNumber
เป็นคลาสแบบใหม่:
- ทั้ง
n1 == n3
และn3 == n1
โทรn3.__eq__
;
- ทั้งสอง
n1 != n3
และการโทรn3 != n1
n3.__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
โอเปอเรเตอร์เพื่อแยกเอกลักษณ์ของออบเจกต์จากการเปรียบเทียบค่า