__Eq__ Python'da nasıl ve hangi sırayla ele alınır?


108

Python, karşılaştırma işleçlerinin sol / sağ sürümlerini sağlamadığından, hangi işlevi çağıracağına nasıl karar verir?

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

Bu her iki __eq__işlevi de çağırıyor gibi görünüyor .

Resmi karar ağacını arıyorum.

Yanıtlar:


129

a == bİfade çağırır A.__eq__bu söz konusu olduğundan,. Kodu içerir self.value == other. İnt'ler kendilerini B'lerle B.__eq__nasıl karşılaştıracaklarını bilmediğinden , Python kendisini int ile nasıl karşılaştıracağını bilip bilmediğini görmek için çağırmaya çalışır .

Kodunuzu hangi değerlerin karşılaştırıldığını gösterecek şekilde değiştirirseniz:

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

yazdıracak:

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 gördüğünde a == başağıdakileri dener.

  • Eğer type(b)yeni bir tarzı sınıfı ve type(b)bir alt sınıfıdır type(a)ve type(b)geçersiz kılmıştır __eq__, o zaman sonucudur b.__eq__(a).
  • Eğer type(a)geçersiz kılınan sahiptir __eq__(yani type(a).__eq__değil object.__eq__), sonra sonucudur a.__eq__(b).
  • Eğer type(b)geçersiz kılmıştır __eq__, o sonuçtur b.__eq__(a).
  • Yukarıdakilerin hiçbiri geçerli değilse, Python aradığı işlemi tekrarlar __cmp__. Varsa, nesneler döndüğü sürece eşittir zero.
  • Son geri dönüş olarak, iff olan ve aynı nesne object.__eq__(a, b)olan Python çağırır .Trueab

Özel yöntemlerden herhangi biri geri NotImplementeddönerse, Python, yöntem yokmuş gibi davranır.

Son adımı dikkatlice not edin: ane başırı yüklenme ne de aşırı ise ==, o zaman a == bile aynıdır a is b.


Gönderen https://eev.ee/blog/2012/03/24/python-faq-equality/


1
Python 3 belgeleri yanlış görünüyor. Açıklama için bugs.python.org/issue4395 ve yamaya bakın . TLDR: rhs'de olsa bile alt sınıf yine de ilk karşılaştırılır.
en fazla

Merhaba kev, güzel gönderi. İlk madde işaretinin nerede belgelendiğini ve neden böyle tasarlandığını açıklayabilir misiniz?
wim

1
Evet, bu python 2 için nerede belgelenmiştir? PEP mi?
Mr_and_Mrs_D

bu cevaba ve eşlik eden yorumlara dayanarak, bu beni öncekinden daha fazla karıştırdı.
Sajuuk

ve btw, __eq__ sadece == geçersiz kılınması için yeterli olmayan bir tür örneğinde bağımlı bir yöntem tanımlamak mı?
Sajuuk

12

Bu soruya Python 3 için güncellenmiş bir cevap yazıyorum.

__eq__Python'da nasıl ve hangi sırayla ele alınır?

a == b

Bu genel olarak anlaşılan, ama her zaman durumda, bu a == bbaşlatır a.__eq__(b), veya type(a).__eq__(a, b).

Açıkça, değerlendirme sırası şöyledir:

  1. Eğer bçeşidini katı bir alt sınıfı (aynı türü) as türü "ve bir sahiptir __eq__, çağrı ve karşılaştırma uygulandığı takdirde değer döndürür
  2. Başka, eğer avar __eq__, diyoruz ve karşılaştırma uygulanırsa iade,
  3. Aksi takdirde, b'leri aramadık __eq__ve onda olup olmadığına bakın, sonra arayın ve karşılaştırma uygulanırsa geri dönün
  4. aksi takdirde, son olarak, aynı karşılaştırmayı kimlik için yapın is.

Yöntem geri dönerse bir karşılaştırmanın uygulanmadığını biliyoruz NotImplemented.

(Python 2'de __cmp__aranan bir yöntem vardı, ancak kullanımdan kaldırıldı ve Python 3'te kaldırıldı.)

Kabul edilen cevabın bu konuda yanlış olduğunu gösteren B alt sınıfı A'ya izin vererek ilk kontrolün davranışını kendimiz için test edelim:

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

sadece B __eq__ calleddönmeden önce yazdırır False.

Bu tam algoritmayı nasıl biliyoruz?

Buradaki diğer cevaplar eksik ve güncel değil, bu yüzden bilgileri güncelleyeceğim ve bunu nasıl kendiniz bulabileceğinizi göstereceğim.

Bu, C düzeyinde ele alınır.

Burada iki farklı kod bitine bakmamız gerekiyor - __eq__sınıf nesneleri için varsayılan ve varsayılanı veya özel olanı kullanıp kullanmadığına bakılmaksızın yöntemi objectarayan ve çağıran kod .__eq____eq__

Varsayılan __eq__

Looking __eq__up alakalı C API docs gösterileri bize bu __eq__tarafından işlenir tp_richcomparehangi - "object"tipi tanımında cpython/Objects/typeobject.ctanımlanır object_richcompareiçin 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;

Yani burada self == othergeri dönersek True, yoksa NotImplementednesneyi iade ederiz . Bu, kendi __eq__yöntemini uygulamayan herhangi bir nesnenin alt sınıfı için varsayılan davranıştır .

Nasıl __eq__çağrılır

Ardından çağıran PyObject_RichCompare işlevi olan C API belgelerini buluyoruz do_richcompare.

Daha sonra C tanımı tp_richcompareiçin yaratılan fonksiyonun "object"tarafından çağrıldığını görüyoruz, o halde buna do_richcomparebiraz daha yakından bakalım.

Bu işlevdeki ilk kontrol, karşılaştırılan nesnelerin koşulları içindir:

  • Hangi değil aynı tip fakat
  • ikincinin türü, birinci türünün bir alt sınıfıdır ve
  • ikinci türün bir __eq__yöntemi var,

daha sonra değiştirilen argümanlar ile diğerinin yöntemini çağırın, eğer uygulanırsa değeri döndürür. Bu yöntem uygulanmazsa devam ederiz ...

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

Daha sonra, __eq__yöntemi ilk türden arayabilir miyiz ve çağırabiliriz. Sonuç NotImplemented olmadığı, yani uygulandığı sürece, onu döndürürüz.

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

Aksi takdirde, diğer türün yöntemini denemediysek ve oradaysa, sonra deneriz ve karşılaştırma uygulanırsa, onu iade ederiz.

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

Son olarak, herhangi birinin türü için uygulanmaması durumunda bir geri dönüş elde ederiz.

Geri dönüş, nesnenin kimliğini, yani bellekte aynı yerde aynı nesne olup olmadığını kontrol eder - bu, aşağıdakilerle aynı kontroldür 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;

Sonuç

Bir karşılaştırmada, önce karşılaştırmanın alt sınıf uygulamasına saygı duyuyoruz.

Daha sonra, ilk nesnenin gerçeklemesiyle, ardından çağrılmadıysa ikinciyle karşılaştırmaya çalışırız.

Son olarak, eşitlik için karşılaştırma amacıyla bir kimlik testi kullanıyoruz.


2020'de Python3 için bir güncelleme sağladığınız için teşekkür ederiz. Matematik operatörlerinin sol / sağ için farklı işleyicilere sahip olduğundan ve her ikisinin de denendiğinden bahsetmeye değer, örneğin a = A (), a + 1, a .__ add__ 'ı çağırır ve 1 + a, A'yı dener. __radd__ eğer int add A'yı işleyemezse, başarısız olan 1 + a'nın nasıl yakalandığına dair herhangi bir fikriniz var mı?
P2000
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.