Bagaimana __eq__ ditangani dengan Python dan bagaimana urutannya?

98

Karena Python tidak menyediakan versi kiri / kanan dari operator pembandingnya, bagaimana cara memutuskan fungsi mana yang akan dipanggil?

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

Ini sepertinya memanggil keduanya __eq__ fungsi tersebut.

Saya mencari pohon keputusan resmi.

PyProg
sumber

Jawaban:

119

The a == bekspresi memanggil A.__eq__, karena ada. Kodenya termasuk self.value == other. Karena int tidak tahu bagaimana membandingkan dirinya dengan B, Python mencoba memanggilB.__eq__ untuk melihat apakah ia tahu bagaimana membandingkan dirinya dengan int.

Jika Anda mengubah kode untuk menunjukkan nilai apa yang dibandingkan:

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

itu akan mencetak:

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

Ketika Python2.x melihatnya a == b, ia mencoba yang berikut ini.

  • Jika type(b)kelas gaya baru, dan type(b)merupakan subkelas dari type(a), dan type(b)telah diganti __eq__, maka hasilnya adalah b.__eq__(a).
  • Jika type(a)telah diganti __eq__(yaitu, type(a).__eq__tidak object.__eq__), maka hasilnya adalaha.__eq__(b) .
  • Jika type(b)sudah diganti __eq__, maka hasilnya adalah b.__eq__(a).
  • Jika tidak ada kasus di atas, Python mengulangi proses yang dicari __cmp__. Jika ada, objeknya sama jika dikembalikan zero.
  • Sebagai fallback terakhir, Python memanggil object.__eq__(a, b), yaitu Trueiff adan bmerupakan objek yang sama.

Jika salah satu metode khusus kembali NotImplemented, Python bertindak seolah-olah metode tersebut tidak ada.

Perhatikan langkah terakhir dengan hati-hati: jika tidak ada aatau bkelebihan beban ==, maka a == bsama dengan a is b.


Dari https://eev.ee/blog/2012/03/24/python-faq-equality/

kev
sumber
1
Uhh sepertinya dokumen python 3 salah. Lihat bugs.python.org/issue4395 dan tambalan untuk klarifikasi. TLDR: subclass masih dibandingkan terlebih dahulu, meskipun itu di rhs.
maks
Halo kev, postingan bagus. Bisakah Anda menjelaskan di mana poin pertama didokumentasikan dan mengapa dirancang seperti itu?
wim
1
Ya, di mana ini didokumentasikan untuk python 2? Apakah ini PEP?
Mr_and_Mrs_D
berdasarkan jawaban ini dan komentar yang menyertainya, ini membuat saya lebih bingung dari sebelumnya.
Sajuuk
dan btw, adalah mendefinisikan metode terikat __eq__ hanya pada contoh beberapa jenis tidak cukup untuk == diganti?
Sajuuk
5

Saya menulis jawaban terbaru untuk Python 3 untuk pertanyaan ini.

Bagaimana __eq__ditangani dengan Python dan bagaimana urutannya?

a == b

Secara umum dipahami, tetapi tidak selalu demikian, yang a == bmemanggil a.__eq__(b), atau type(a).__eq__(a, b).

Secara eksplisit, urutan evaluasi adalah:

  1. jika btipe adalah subkelas ketat (bukan tipe yang sama) dari atipe dan memiliki __eq__, panggil dan kembalikan nilainya jika perbandingan diimplementasikan,
  2. lain, jika asudah __eq__, panggil dan kembalikan jika perbandingan diterapkan,
  3. lain, lihat apakah kita tidak memanggil b __eq__dan ia memilikinya, lalu panggil dan kembalikan jika perbandingan diterapkan,
  4. lain, akhirnya, lakukan perbandingan identitas, sama seperti perbandingan is.

Kami tahu jika perbandingan tidak diterapkan jika metode tersebut kembali NotImplemented.

(Di Python 2, ada __cmp__metode yang dicari, tetapi tidak digunakan lagi dan dihapus di Python 3.)

Mari kita uji perilaku pemeriksaan pertama untuk diri kita sendiri dengan membiarkan B subclass A, yang menunjukkan bahwa jawaban yang diterima salah dalam hitungan ini:

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

yang hanya mencetak B __eq__ calledsebelum dikembalikan False.

Bagaimana kita mengetahui algoritma lengkap ini?

Jawaban lain di sini tampaknya tidak lengkap dan ketinggalan zaman, jadi saya akan memperbarui informasi dan menunjukkan kepada Anda bagaimana Anda dapat mencarinya sendiri.

Ini ditangani di tingkat C.

Kita perlu melihat dua bit kode yang berbeda di sini - default __eq__untuk objek kelas object, dan kode yang mencari dan memanggil __eq__metode terlepas dari apakah metode itu menggunakan default __eq__atau kustom.

Default __eq__

Mencari __eq__di dokumen C api yang relevan menunjukkan kepada kita bahwa __eq__ditangani oleh tp_richcompare- yang dalam "object"definisi tipe di cpython/Objects/typeobject.cdidefinisikan object_richcompareuntuk case 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;

Jadi di sini, jika self == otherkita mengembalikan True, kalau tidak kita mengembalikan NotImplementedobjeknya. Ini adalah perilaku default untuk setiap subclass objek yang tidak mengimplementasikan __eq__metodenya sendiri .

Bagaimana __eq__dipanggil

Kemudian kami menemukan dokumen C API, fungsi PyObject_RichCompare , yang memanggil do_richcompare.

Kemudian kita melihat bahwa tp_richcomparefungsi yang dibuat untuk "object"definisi C dipanggil oleh do_richcompare, jadi mari kita lihat itu lebih dekat.

Pemeriksaan pertama dalam fungsi ini adalah untuk kondisi objek yang dibandingkan:

  • yang tidak jenis yang sama, tetapi
  • tipe kedua adalah subclass dari tipe pertama, dan
  • tipe kedua memiliki __eq__metode,

lalu panggil metode lain dengan argumen bertukar, mengembalikan nilai jika diterapkan. Jika metode itu tidak diterapkan, kami melanjutkan ...

    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);

Selanjutnya kita melihat apakah kita dapat mencari __eq__metode dari tipe pertama dan memanggilnya. Selama hasilnya tidak NotImplemented, yaitu diimplementasikan, kami mengembalikannya.

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

Lain jika kita tidak mencoba metode tipe lain dan metode itu ada, kita kemudian mencobanya, dan jika perbandingan diterapkan, kita mengembalikannya.

    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);
    }

Akhirnya, kami mendapatkan fallback jika tidak diterapkan untuk salah satu jenisnya.

Pemeriksaan fallback untuk identitas objek, yaitu, apakah itu objek yang sama di tempat yang sama dalam memori - ini adalah pemeriksaan yang sama untuk 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;

Kesimpulan

Sebagai perbandingan, kami menghormati implementasi subclass dari perbandingan terlebih dahulu.

Kemudian kami mencoba membandingkan dengan implementasi objek pertama, kemudian dengan implementasi objek kedua jika tidak dipanggil.

Akhirnya kami menggunakan tes identitas untuk perbandingan kesetaraan.

Aaron Hall
sumber