วิธีที่สวยงามในการสนับสนุนความเท่าเทียม ("ความเท่าเทียมกัน") ในคลาส Python


421

เมื่อเขียนคลาสที่กำหนดเองมักเป็นสิ่งสำคัญที่จะอนุญาตให้มีความเท่าเทียมกันโดยใช้ตัวดำเนินการ==และ !=ใน Python สิ่งนี้เกิดขึ้นได้โดยการใช้__eq__และ__ne__วิธีพิเศษตามลำดับ วิธีที่ง่ายที่สุดที่ฉันพบว่าทำคือวิธีการต่อไปนี้:

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)

คุณรู้วิธีที่สง่างามมากขึ้นในการทำเช่นนี้? คุณรู้ถึงข้อเสียใด ๆ ในการใช้วิธีการเปรียบเทียบข้างต้น__dict__หรือไม่?

หมายเหตุ : การชี้แจงเล็กน้อย - เมื่อ__eq__และ__ne__ไม่ได้กำหนดคุณจะพบพฤติกรรมนี้:

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False

นั่นคือa == bประเมินFalseเพราะมันทำงานจริง ๆa is bการทดสอบตัวตน (เช่น " aวัตถุเดียวกันbคืออะไร?")

เมื่อใด__eq__และ__ne__มีการกำหนดไว้คุณจะพบพฤติกรรมนี้ (ซึ่งเป็นพฤติกรรมที่เราตามมา):

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True

6
+1, เพราะฉันไม่รู้ว่า dict ใช้ความเสมอภาคของ memberwise สำหรับ ==, ฉันคิดว่ามันนับแค่เท่ากับ dicts วัตถุเดียวกันเท่านั้น ฉันเดาว่ามันชัดเจนเนื่องจาก Python มีisโอเปอเรเตอร์เพื่อแยกเอกลักษณ์ของออบเจกต์จากการเปรียบเทียบค่า
SingleNegationElimination

5
ฉันคิดว่าคำตอบที่ยอมรับนั้นได้รับการแก้ไขหรือกำหนดใหม่ให้กับคำตอบของ Algorias เพื่อให้การตรวจสอบประเภทที่เข้มงวดนั้นได้ดำเนินการ
สูงสุด

1
และตรวจสอบให้แน่ใจว่าแฮชถูกแทนที่ทับstackoverflow.com/questions/1608842/ …
Alex Punnen

คำตอบ:


328

พิจารณาปัญหาง่ายๆนี้:

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

3
hash(tuple(sorted(self.__dict__.items())))จะไม่ทำงานหากมีวัตถุที่ไม่สามารถแฮชได้ในค่าของself.__dict__(เช่นหากคุณลักษณะใด ๆ ของวัตถุนั้นถูกตั้งค่าเป็น, พูด, a list)
สูงสุด

3
จริง แต่ถ้าคุณมีวัตถุที่ไม่แน่นอนใน vars ของคุณ () วัตถุทั้งสองนั้นไม่เท่ากันจริง ๆ ...
4173 Tal

12
สรุปที่ดี แต่คุณควรใช้__ne__ใช้==__eq__แทน
Florian Brucker

1
ข้อสังเกตสามข้อ: 1. ใน Python 3 ไม่จำเป็นต้องนำ__ne__ไปใช้อีกต่อไป: "โดยค่าเริ่มต้น__ne__()ผู้ได้รับมอบหมายจะเข้า__eq__()และกลับผลลัพธ์เว้นแต่จะเป็นNotImplemented" 2. หากหนึ่งยังคงต้องการที่จะใช้__ne__, การใช้งานทั่วไปมากขึ้น (หนึ่งใช้โดยงูหลาม 3 ผมคิดว่า) x = self.__eq__(other); if x is NotImplemented: return x; else: return not xเป็น: 3. การกำหนด__eq__และ__ne__การใช้งานเป็นสิ่งที่ไม่ดี: if isinstance(other, type(self)):ให้การโทร22 __eq__และ 10 __ne__ขณะที่if isinstance(self, type(other)):จะให้การโทร16 __eq__และ 6 __ne__
Maggyero

4
เขาถามถึงความสง่างาม แต่เขาแข็งแกร่ง
GregNash

201

คุณต้องระวังการสืบทอด:

>>> class Foo:
    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

>>> class Bar(Foo):pass

>>> b = Bar()
>>> f = Foo()
>>> f == b
True
>>> b == f
False

ตรวจสอบประเภทอย่างเข้มงวดยิ่งขึ้นเช่นนี้

def __eq__(self, other):
    if type(other) is type(self):
        return self.__dict__ == other.__dict__
    return False

นอกจากนั้นวิธีการของคุณจะทำงานได้ดีนั่นคือวิธีการพิเศษสำหรับ


นี่เป็นจุดที่ดี ฉันคิดว่ามันคุ้มค่าที่จะทราบว่าการแบ่งประเภทย่อยที่สร้างขึ้นในประเภทยังช่วยให้เกิดความเท่าเทียมกันในทิศทางใดทิศทางหนึ่งดังนั้นการตรวจสอบว่ามันเป็นประเภทเดียวกันอาจไม่เป็นที่พึงปรารถนา
gotgenes

12
ฉันขอแนะนำให้ส่งคืนการใช้งานหากประเภทนั้นแตกต่างกันโดยมอบหมายการเปรียบเทียบกับ rhs
สูงสุด

4
การเปรียบเทียบ @max ไม่จำเป็นต้องทำทางด้านซ้ายมือ (LHS) ไปทางด้านขวามือ (RHS) จากนั้นเลือก RHS ถึง LHS ดูstackoverflow.com/a/12984987/38140 ยังคงกลับมาNotImplementedตามที่คุณแนะนำจะทำให้เกิดsuperclass.__eq__(subclass)ซึ่งเป็นพฤติกรรมที่ต้องการ
gotgenes

4
หากคุณมีสมาชิกเป็นจำนวนมากและมีวัตถุคัดลอกไม่มากนักคุณมักจะเพิ่มการทดสอบตัวตนif other is selfเบื้องต้น วิธีนี้จะหลีกเลี่ยงการเปรียบเทียบพจนานุกรมที่ยาวกว่าและสามารถประหยัดได้มากเมื่อใช้วัตถุเป็นคีย์พจนานุกรม
Dane White

2
และอย่าลืมติดตั้ง__hash__()
Dane White

161

วิธีที่คุณอธิบายคือวิธีที่ฉันทำมันมาตลอด เนื่องจากมันเป็นแบบทั่วไปโดยสิ้นเชิงคุณสามารถแยกฟังก์ชันการทำงานออกเป็นคลาส mixin และสืบทอดในคลาสที่คุณต้องการใช้ฟังก์ชันนั้น

class CommonEqualityMixin(object):

    def __eq__(self, other):
        return (isinstance(other, self.__class__)
            and self.__dict__ == other.__dict__)

    def __ne__(self, other):
        return not self.__eq__(other)

class Foo(CommonEqualityMixin):

    def __init__(self, item):
        self.item = item

6
+1: รูปแบบกลยุทธ์เพื่อให้เปลี่ยนได้ง่ายในคลาสย่อย
S.Lott

3
isinstance ดูด ทำไมต้องตรวจสอบ ทำไมไม่เพียง แต่ตัวเอง __ dict__ == อื่น ๆ . ict dict__?
nosklo

3
@nosklo: ฉันไม่เข้าใจ .. จะเกิดอะไรขึ้นถ้าวัตถุสองอย่างจากคลาสที่ไม่เกี่ยวข้องทั้งหมดมีคุณลักษณะเดียวกัน
สูงสุด

1
ฉันคิดว่า nokslo แนะนำให้ข้าม isinstance ในกรณีนี้คุณจะไม่ทราบอีกว่าotherเป็นประเภทย่อยself.__class__หรือไม่
สูงสุด

10
ปัญหาอีกประการหนึ่งของ__dict__การเปรียบเทียบคือถ้าคุณมีแอตทริบิวต์ที่คุณไม่ต้องการพิจารณาในคำจำกัดความความเท่าเทียม (เช่นรหัสวัตถุที่ไม่ซ้ำกันหรือข้อมูลเมตาเช่นการประทับเวลาที่สร้างขึ้น)
Adam Parkin

14

ไม่ใช่คำตอบโดยตรง แต่ดูเหมือนว่าจะมีความเกี่ยวข้องมากพอที่จะได้รับการแก้ไข ตัดตรงจากเอกสาร ...


functools.total_ordering (CLS)

ให้ชั้นเรียนที่กำหนดวิธีการเปรียบเทียบการเปรียบเทียบอย่างน้อยหนึ่งวิธีมัณฑนากรชั้นนี้ให้ส่วนที่เหลือ สิ่งนี้ช่วยลดความยุ่งยากที่เกี่ยวข้องในการระบุการดำเนินการเปรียบเทียบที่เป็นไปได้ทั้งหมด:

ชั้นต้องกำหนดหนึ่ง__lt__(), __le__(), หรือ__gt__() __ge__()นอกจากนี้คลาสควรระบุ__eq__()วิธีการ

ใหม่ในรุ่น 2.7

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

1
อย่างไรก็ตาม total_ordering มีข้อผิดพลาดเล็กน้อย: regebro.wordpress.com/2010/12/13/ … ระวัง !
Mr_and_Mrs_D

8

คุณไม่จำเป็นต้องแทนที่ทั้งสองอย่าง__eq__และ__ne__คุณสามารถแทนที่เท่านั้น__cmp__แต่สิ่งนี้จะส่งผลต่อผลลัพธ์ของ ==,! ==, <,> และอื่น ๆ

isทดสอบเอกลักษณ์ของวัตถุ นี่หมายความว่าisb จะเป็นTrueในกรณีที่ a และ b ทั้งคู่เก็บการอ้างอิงไปยังวัตถุเดียวกัน ในไพ ธ อนคุณจะต้องมีการอ้างอิงไปยังอ็อบเจกต์ในตัวแปรไม่ใช่วัตถุที่แท้จริงดังนั้นสำหรับ a คือ b จะเป็นจริงวัตถุในนั้นควรจะอยู่ในตำแหน่งหน่วยความจำเดียวกัน คุณจะอธิบายเกี่ยวกับพฤติกรรมนี้ได้อย่างไรและที่สำคัญที่สุด

แก้ไข: ฉันไม่ทราบว่า__cmp__ถูกลบออกจาก python 3 เพื่อหลีกเลี่ยง


เพราะบางครั้งคุณมีคำจำกัดความของความเท่าเทียมกันที่แตกต่างกันสำหรับวัตถุของคุณ
Ed S.

เป็นผู้ประกอบการช่วยให้คุณมีคำตอบที่ล่ามตัวตนวัตถุ แต่คุณยังคงมีอิสระที่จะแสดงให้คุณดูบนความเท่าเทียมกันโดยเอาชนะCMP
วาชิล

7
ใน Python 3 "ฟังก์ชัน cmp () หายไปและไม่สนับสนุนวิธีพิเศษ __cmp __ () อีกต่อไป" is.gd/aeGv
gotgenes

4

จากคำตอบนี้: https://stackoverflow.com/a/30676267/541136ฉันได้แสดงให้เห็นแล้วในขณะที่มันถูกต้องเพื่อกำหนด__ne__ในแง่__eq__- แทน

def __ne__(self, other):
    return not self.__eq__(other)

คุณควรใช้:

def __ne__(self, other):
    return not self == other

2

ฉันคิดว่าทั้งสองคำที่คุณกำลังมองหาคือความเท่าเทียมกัน (==) และตัวตน (คือ) ตัวอย่างเช่น:

>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True       <-- a and b have values which are equal
>>> a is b
False      <-- a and b are not the same list object

1
อาจยกเว้นว่าจะสร้างคลาสที่เปรียบเทียบเพียงสองรายการแรกในสองรายการเท่านั้นและหากรายการเหล่านั้นเท่ากันจะประเมินเป็น True นี่คือความเท่าเทียมกันฉันคิดว่าไม่เท่าเทียมกัน สมบูรณ์แบบในeqยังคง
gotgenes

อย่างไรก็ตามฉันเห็นด้วยว่า "เป็น" เป็นการทดสอบตัวตน
gotgenes

1

การทดสอบ 'is' จะทดสอบหาตัวตนโดยใช้ฟังก์ชั่น builtin 'id ()' ซึ่งส่งกลับที่อยู่หน่วยความจำของวัตถุดังนั้นจึงไม่สามารถโหลดได้มากเกินไป

อย่างไรก็ตามในกรณีของการทดสอบความเท่าเทียมกันของคลาสคุณอาจต้องการเข้มงวดมากขึ้นเล็กน้อยเกี่ยวกับการทดสอบของคุณและเปรียบเทียบคุณลักษณะข้อมูลในชั้นเรียนของคุณเท่านั้น:

import types

class ComparesNicely(object):

    def __eq__(self, other):
        for key, value in self.__dict__.iteritems():
            if (isinstance(value, types.FunctionType) or 
                    key.startswith("__")):
                continue

            if key not in other.__dict__:
                return False

            if other.__dict__[key] != value:
                return False

         return True

รหัสนี้จะเปรียบเทียบเฉพาะสมาชิกข้อมูลที่ไม่ใช่ฟังก์ชันในชั้นเรียนของคุณรวมถึงข้ามสิ่งที่เป็นส่วนตัวซึ่งโดยทั่วไปเป็นสิ่งที่คุณต้องการ ในกรณีของ Plain Python Object ฉันมีคลาสพื้นฐานซึ่งใช้ __init__, __str__, __repr__ และ __eq__ ดังนั้นวัตถุ POPO ของฉันจึงไม่ต้องแบกภาระของตรรกะพิเศษ (และส่วนใหญ่เหมือนกัน)


Bit nitpicky แต่ 'คือ' การทดสอบโดยใช้ id () เฉพาะในกรณีที่คุณยังไม่ได้กำหนดฟังก์ชั่นสมาชิก is_ () ของคุณ (2.3+) [ docs.python.org/library/operator.html]
แล้ว

ฉันถือว่าโดย "แทนที่" คุณหมายถึงลิง patching โมดูลผู้ประกอบการ ในกรณีนี้ข้อความของคุณไม่ถูกต้องทั้งหมด โมดูลตัวดำเนินการถูกจัดเตรียมเพื่อความสะดวกและการแทนที่วิธีการเหล่านั้นไม่ส่งผลกระทบต่อพฤติกรรมของตัวดำเนินการ "เป็น" การเปรียบเทียบโดยใช้ "คือ" จะใช้ id () ของวัตถุเสมอสำหรับการเปรียบเทียบพฤติกรรมนี้ไม่สามารถเขียนทับได้ นอกจากนี้ฟังก์ชั่นสมาชิก is_ ก็ไม่มีผลต่อการเปรียบเทียบ
mcrute

mcrute - ฉันพูดเร็วเกินไป (และไม่ถูกต้อง) คุณพูดถูก
จ่าย

นี่เป็นทางออกที่ดีมากโดยเฉพาะอย่างยิ่งเมื่อ__eq__จะประกาศในCommonEqualityMixin(ดูคำตอบอื่น ๆ ) ฉันพบสิ่งนี้มีประโยชน์อย่างยิ่งเมื่อเปรียบเทียบอินสแตนซ์ของคลาสที่ได้จาก Base ใน SQLAlchemy เพื่อไม่ให้เปรียบเทียบ_sa_instance_stateผมเปลี่ยนไปkey.startswith("__")): key.startswith("_")):ฉันมีการตอบกลับบางอย่างในพวกเขาและคำตอบจาก Algorias ทำให้เกิดการเรียกซ้ำแบบไม่รู้จบ ดังนั้นฉันตั้งชื่อ backreferences ทั้งหมดที่เริ่มต้นด้วย'_'เพื่อให้พวกเขาข้ามในระหว่างการเปรียบเทียบ หมายเหตุ: ในการเปลี่ยนแปลงหลาม 3.x ไปiteritems() items()
Wookie88

@mcrute โดยปกติแล้ว__dict__อินสแตนซ์จะไม่มีสิ่งใดที่เริ่มต้นด้วย__เว้นแต่จะถูกกำหนดโดยผู้ใช้ สิ่งที่ชอบ__class__, __init__ฯลฯ ไม่ได้อยู่ในอินสแตนซ์ของ__dict__แต่ในระดับ __dict__OTOH, คุณลักษณะส่วนตัวได้อย่างง่ายดายสามารถเริ่มต้นด้วยและอาจจะนำมาใช้สำหรับ__ __eq__คุณสามารถอธิบายให้ชัดเจนว่าคุณพยายามหลีกเลี่ยงอะไรเมื่อข้าม__แอททริบิวต์ที่ได้รับการแก้ไข?
สูงสุด

1

แทนที่จะใช้คลาสย่อย / มิกซ์อินฉันชอบใช้มัณฑนากรทั่วไป

def comparable(cls):
    """ Class decorator providing generic comparison functionality """

    def __eq__(self, other):
        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__

    def __ne__(self, other):
        return not self.__eq__(other)

    cls.__eq__ = __eq__
    cls.__ne__ = __ne__
    return cls

การใช้งาน:

@comparable
class Number(object):
    def __init__(self, x):
        self.x = x

a = Number(1)
b = Number(1)
assert a == b

0

สิ่งนี้รวมความคิดเห็นเกี่ยวกับคำตอบของ Algorias และเปรียบเทียบวัตถุด้วยคุณลักษณะเดียวเพราะฉันไม่สนใจเรื่อง dict ทั้งหมด hasattr(other, "id")ต้องเป็นจริง แต่ฉันรู้ว่าเป็นเพราะฉันตั้งไว้ในตัวสร้าง

def __eq__(self, other):
    if other is self:
        return True

    if type(other) is not type(self):
        # delegate to superclass
        return NotImplemented

    return other.id == self.id
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.