__eq__ จัดการอย่างไรใน Python และเรียงลำดับอย่างไร


107

เนื่องจาก Python ไม่มีตัวดำเนินการเปรียบเทียบเวอร์ชันซ้าย / ขวาจึงตัดสินใจได้อย่างไรว่าจะเรียกใช้ฟังก์ชันใด

class A(object):
    def __eq__(self, other):
        print "A __eq__ called"
        return self.value == other
class B(object):
    def __eq__(self, other):
        print "B __eq__ called"
        return self.value == other

>>> a = A()
>>> a.value = 3
>>> b = B()
>>> b.value = 4
>>> a == b
"A __eq__ called"
"B __eq__ called"
False

ดูเหมือนว่าจะเรียกใช้ทั้งสอง__eq__ฟังก์ชัน

ฉันกำลังมองหาแผนผังการตัดสินใจอย่างเป็นทางการ

คำตอบ:


129

a == bแสดงออกเรียกA.__eq__เพราะมันมีอยู่ self.value == otherรหัสรวมถึง เนื่องจาก int ไม่รู้ว่าจะเปรียบเทียบตัวเองกับ B ได้อย่างไร Python จึงพยายามเรียกร้องB.__eq__เพื่อดูว่ามันรู้วิธีเปรียบเทียบตัวเองกับ int หรือไม่

หากคุณแก้ไขรหัสของคุณเพื่อแสดงว่ามีการเปรียบเทียบค่าใด:

class A(object):
    def __eq__(self, other):
        print("A __eq__ called: %r == %r ?" % (self, other))
        return self.value == other
class B(object):
    def __eq__(self, other):
        print("B __eq__ called: %r == %r ?" % (self, other))
        return self.value == other

a = A()
a.value = 3
b = B()
b.value = 4
a == b

มันจะพิมพ์:

A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?

69

เมื่อ Python2.x เห็นa == bมันจะลองสิ่งต่อไปนี้

  • หากtype(b)เป็นชั้นแบบใหม่และtype(b)เป็น subclass ของtype(a)และtype(b)ได้แทนที่แล้วผลที่ได้คือ__eq__b.__eq__(a)
  • หากtype(a)มีการแทนที่__eq__(นั่นคือtype(a).__eq__ไม่ได้object.__eq__) a.__eq__(b)แล้วผลที่ได้คือ
  • หากtype(b)ได้แทนที่แล้วผลที่ได้คือ__eq__b.__eq__(a)
  • __cmp__หากไม่มีการดังกล่าวเป็นกรณีที่งูหลามซ้ำกระบวนการที่กำลังมองหา ถ้ามีวัตถุที่มีค่าเท่ากัน IFF zeroจะส่งกลับ
  • ในฐานะที่เป็นทางเลือกสุดท้าย Python เรียกobject.__eq__(a, b)ซึ่งเป็นTrueiff aและbเป็นวัตถุเดียวกัน

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

โปรดทราบว่าขั้นตอนสุดท้ายอย่างระมัดระวัง: ถ้าค่าaมิได้boverloads ==แล้วเป็นเช่นเดียวกับa == ba is b


จากhttps://eev.ee/blog/2012/03/24/python-faq-equality/


1
ดูเหมือนว่าเอกสาร python 3 จะไม่ถูกต้อง ดูbugs.python.org/issue4395และโปรแกรมแก้ไขสำหรับคำชี้แจง TLDR: ยังคงเปรียบเทียบคลาสย่อยก่อนแม้ว่าจะอยู่ใน rhs ก็ตาม
สูงสุด

สวัสดี kev โพสต์ที่ดี คุณช่วยอธิบายได้ไหมว่าสัญลักษณ์แสดงหัวข้อย่อยแรกถูกบันทึกไว้ที่ไหนและเหตุใดจึงออกแบบมาเช่นนั้น
Wim

1
ใช่เอกสารนี้สำหรับ python 2 อยู่ที่ไหน เป็น PEP หรือไม่?
Mr_and_Mrs_D

จากคำตอบนี้และมาพร้อมกับความคิดเห็นสิ่งนี้ทำให้ฉันสับสนมากกว่าเดิม
Sajuuk

และ btw กำหนดวิธีการผูกไว้__eq__ กับอินสแตนซ์ของบางประเภทไม่เพียงพอสำหรับ == ที่จะลบล้าง?
Sajuuk

12

ฉันกำลังเขียนคำตอบที่อัปเดตสำหรับ Python 3 สำหรับคำถามนี้

มี__eq__การจัดการอย่างไรใน Python และเรียงลำดับอย่างไร?

a == b

มันเป็นที่เข้าใจกันโดยทั่วไป แต่ไม่เสมอกรณีที่a == bจะเรียกหรือa.__eq__(b)type(a).__eq__(a, b)

อย่างชัดเจนลำดับของการประเมินคือ:

  1. ถ้าbประเภทเป็นคลาสย่อยที่เข้มงวด (ไม่ใช่ประเภทเดียวกัน) ของaประเภทและมีให้__eq__เรียกและส่งคืนค่าหากมีการใช้การเปรียบเทียบ
  2. อื่นถ้าaมี__eq__ให้เรียกและส่งคืนหากมีการใช้การเปรียบเทียบ
  3. อื่นดูว่าเราไม่ได้เรียก b __eq__และมันมีแล้วโทรและส่งคืนหากมีการใช้การเปรียบเทียบ
  4. อื่นในที่สุดก็ทำเปรียบเทียบลักษณะ, isการเปรียบเทียบเช่นเดียวกับ

NotImplementedเรารู้ว่าถ้าเปรียบเทียบไม่ได้ดำเนินการหากผลตอบแทนที่วิธีการ

(ใน Python 2 มีไฟล์ __cmp__เมธอดที่ถูกมองหา แต่ถูกเลิกใช้งานและลบออกใน Python 3)

ลองทดสอบพฤติกรรมของเช็คแรกด้วยตัวเราเองโดยปล่อยให้ B subclass A ซึ่งแสดงว่าคำตอบที่ยอมรับนั้นผิดในการนับนี้:

class A:
    value = 3
    def __eq__(self, other):
        print('A __eq__ called')
        return self.value == other.value

class B(A):
    value = 4
    def __eq__(self, other):
        print('B __eq__ called')
        return self.value == other.value

a, b = A(), B()
a == b

ซึ่งจะพิมพ์B __eq__ calledก่อนกลับFalseเท่านั้น

เราจะรู้อัลกอริทึมทั้งหมดนี้ได้อย่างไร?

คำตอบอื่น ๆ ที่นี่ดูเหมือนจะไม่สมบูรณ์และล้าสมัยดังนั้นฉันจะอัปเดตข้อมูลและแสดงให้คุณเห็นว่าคุณจะค้นหาสิ่งนี้ด้วยตัวคุณเองได้อย่างไร

สิ่งนี้ได้รับการจัดการที่ระดับ C

เราต้องดูรหัสสองบิตที่แตกต่างกันที่นี่ - ค่าเริ่มต้น__eq__สำหรับวัตถุของคลาสobjectและรหัสที่ค้นหาและเรียกใช้__eq__เมธอดโดยไม่คำนึงว่าจะใช้ค่าเริ่มต้นหรือไม่__eq__หรือแบบกำหนดเอง

ค่าเริ่มต้น __eq__

มอง__eq__ขึ้นมาในที่เกี่ยวข้องเอกสาร C APIแสดงให้เราเห็นว่า__eq__จะถูกจัดการโดยtp_richcompare- ซึ่งใน"object"การกำหนดประเภทในการcpython/Objects/typeobject.cกำหนดไว้ในสำหรับobject_richcomparecase Py_EQ:

    case Py_EQ:
        /* Return NotImplemented instead of False, so if two
           objects are compared, both get a chance at the
           comparison.  See issue #1393. */
        res = (self == other) ? Py_True : Py_NotImplemented;
        Py_INCREF(res);
        break;

ที่นี่ถ้าself == otherเรากลับมาTrueก็จะส่งคืนNotImplementedวัตถุ นี่เป็นลักษณะการทำงานเริ่มต้นสำหรับคลาสย่อยของอ็อบเจ็กต์ที่ไม่ได้ใช้__eq__วิธีการของตัวเอง

วิธีการ__eq__ได้รับเรียกว่า

จากนั้นเราจะพบเอกสาร C API ที่PyObject_RichComparedo_richcompareฟังก์ชั่นที่โทร

จากนั้นเราจะเห็นว่าtp_richcompareฟังก์ชั่นที่สร้างขึ้นสำหรับ"object"นิยาม C ถูกเรียกโดยdo_richcompareดังนั้นเรามาดูกันอย่างละเอียดอีกนิด

การตรวจสอบครั้งแรกในฟังก์ชั่นนี้มีไว้สำหรับเงื่อนไขของการเปรียบเทียบวัตถุ:

  • มีไม่ชนิดเดียวกัน แต่
  • ประเภทที่สองคือคลาสย่อยของประเภทแรกและ
  • ประเภทที่สองมี__eq__วิธีการ

จากนั้นเรียกใช้เมธอดของอีกฝ่ายโดยมีการแลกเปลี่ยนอาร์กิวเมนต์ส่งคืนค่าหากใช้งาน หากวิธีนี้ไม่ได้ใช้เราดำเนินการต่อ ...

    if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
        PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
        (f = Py_TYPE(w)->tp_richcompare) != NULL) {
        checked_reverse_op = 1;
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);

ต่อไปเราจะดูว่าเราสามารถค้นหา__eq__วิธีการจากประเภทแรกและเรียกมันได้หรือไม่ ตราบเท่าที่ผลลัพธ์ไม่ได้นำไปใช้นั่นคือมีการนำไปใช้งานเราจะส่งคืน

    if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);

หากเราไม่ได้ลองใช้วิธีการของประเภทอื่นและอยู่ที่นั่นเราจะลองและหากการเปรียบเทียบถูกนำไปใช้เราจะส่งคืน

    if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }

สุดท้ายเราได้รับทางเลือกในกรณีที่ไม่ได้ใช้กับประเภทใดประเภทหนึ่ง

ทางเลือกจะตรวจสอบข้อมูลประจำตัวของวัตถุนั่นคือว่าเป็นวัตถุเดียวกันที่ตำแหน่งเดียวกันในหน่วยความจำหรือไม่ซึ่งเป็นการตรวจสอบเช่นเดียวกับself is other:

    /* If neither object implements it, provide a sensible default
       for == and !=, but raise an exception for ordering. */
    switch (op) {
    case Py_EQ:
        res = (v == w) ? Py_True : Py_False;
        break;

สรุป

ในการเปรียบเทียบเราเคารพการใช้คลาสย่อยของการเปรียบเทียบก่อน

จากนั้นเราลองเปรียบเทียบกับการนำไปใช้งานของออบเจ็กต์แรกจากนั้นกับสิ่งที่สองถ้ามันไม่ถูกเรียก

ในที่สุดเราก็ใช้การทดสอบเอกลักษณ์เพื่อเปรียบเทียบความเท่าเทียมกัน


ขอขอบคุณที่ให้การอัปเดต Python3 ในปี 2020 อาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าตัวดำเนินการทางคณิตศาสตร์มีตัวจัดการที่แตกต่างกันสำหรับซ้าย / ขวาและลองทั้งคู่เช่น a = A (), a + 1 เรียก a .__ add__ และ 1 + a พยายาม A __radd__ ถ้า int add ไม่สามารถจัดการกับ A. ความคิดใด ๆ เกี่ยวกับการจับ 1 + a ที่ล้มเหลวซึ่งนำไปสู่ ​​A .__ radd__?
P2000
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.