__Eq__ को पायथन में कैसे और किस क्रम में संभाला जाता है?


96

चूंकि पायथन अपने तुलनात्मक ऑपरेटरों के बाएं / दाएं संस्करणों को प्रदान नहीं करता है, यह कैसे तय करता है कि किस फ़ंक्शन को कॉल करना है?

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__कार्यों को कॉल करने के लिए लगता है ।

मैं आधिकारिक निर्णय वृक्ष की तलाश में हूं।

जवाबों:


119

a == bअभिव्यक्ति का आह्वान A.__eq__है, क्योंकि यह मौजूद है। इसके कोड में शामिल हैं self.value == other। चूंकि int खुद को B की तुलना करना नहीं जानता है, पायथन 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)एक उपवर्ग है 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__। यदि यह मौजूद है, तो वस्तुएं समान हैं यदि यह वापस आती है zero
  • अंतिम गिरावट के रूप में, पायथन कॉल करता है object.__eq__(a, b), जो कि Trueiff है aऔर bएक ही वस्तु है।

यदि कोई विशेष विधि वापस आती है NotImplemented, तो पायथन कार्य करता है, हालांकि विधि मौजूद नहीं थी।

ध्यान दें कि अंतिम चरण ध्यान से: यदि न तो और न aही bअधिभार ==, तो a == bजैसा है वैसा ही है a is b


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


1
उह यह लगता है कि अजगर 3 डॉक्स गलत थे। Bugs.python.org/issue4395 और स्पष्टीकरण के लिए पैच देखें । TLDR: सबक्लास अभी भी पहले की तुलना में, भले ही वह rhs पर हो।
अधिकतम

हाय केव, अच्छी पोस्ट। क्या आप बता सकते हैं कि पहली गोली बिंदु कहाँ प्रलेखित है और इसे इस तरह क्यों बनाया गया है?
विम

1
हाँ, यह अजगर 2 के लिए कहां प्रलेखित है? क्या यह एक पीईपी है?
Mr_and_Mrs_D

इस जवाब के आधार पर और टिप्पणियों के साथ, इसने मुझे पहले से अधिक भ्रमित कर दिया।
सजुकु

और btw, __eq__ कुछ प्रकार के उदाहरण पर एक बाध्य विधि को परिभाषित करने के लिए == के लिए पर्याप्त नहीं है;
सजुकु

3

मैं इस प्रश्न के लिए पायथन 3 के लिए एक अद्यतन उत्तर लिख रहा हूं।

__eq__पायथन में कैसे और किस क्रम में संभाला जाता है?

a == b

यह आमतौर पर समझा जाता है, लेकिन हमेशा ऐसा नहीं होता है, जो a == bआक्रमण करता है a.__eq__(b), या type(a).__eq__(a, b)

स्पष्ट रूप से, मूल्यांकन का क्रम है:

  1. यदि bप्रकार एक सख्त उपवर्ग है (उसी प्रकार का नहीं) का aप्रकार है और एक है __eq__, तो इसे कॉल करें और यदि मूल्य लागू किया जाता है, तो तुलना वापस करें।
  2. और, यदि aहै __eq__, तो इसे कॉल करें और यदि तुलना की गई है, तो इसे वापस कर दें।
  3. इसके अलावा, देखें कि क्या हमने बी को कॉल नहीं किया है __eq__और यह उसके पास है, तो कॉल करें और अगर तुलना की जाती है तो उसे वापस करें,
  4. अंत में, पहचान के लिए तुलना, के रूप में एक ही तुलना करें is

हमें पता है कि अगर विधि वापस आती है तो तुलना लागू नहीं होती है NotImplemented

(अजगर 2 में, एक था __cmp__ विधि थी जिसकी तलाश की गई थी, लेकिन इसे हटा दिया गया और अजगर 3 में हटा दिया गया)

आइए बी उपवर्ग ए देकर अपने लिए पहले चेक के व्यवहार का परीक्षण करें, जो दर्शाता है कि इस गिनती पर स्वीकृत उत्तर गलत है:

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

हम इस पूर्ण एल्गोरिथ्म को कैसे जानते हैं?

यहां अन्य उत्तर अपूर्ण और पुराने हैं, इसलिए मैं जानकारी को अपडेट करने जा रहा हूं और आपको दिखाता हूं कि आप अपने लिए यह कैसे देख सकते हैं।

यह सी स्तर पर संभाला जाता है।

हमें यहां कोड के दो अलग-अलग बिट्स देखने की जरूरत है - __eq__कक्षा की वस्तुओं के लिए डिफ़ॉल्ट object, और कोड जो दिखता है और कॉल __eq__करता है चाहे वह डिफ़ॉल्ट __eq__या कस्टम का उपयोग करता हो ।

चूक __eq__

खोज रहे हैं __eq__में प्रासंगिक सी एपीआई डॉक्स से पता चलता है हमें कि __eq__द्वारा नियंत्रित किया जाता tp_richcompareहै, जिसमें - "object"प्रकार परिभाषा cpython/Objects/typeobject.cमें परिभाषित किया गया है object_richcompareके लिए 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;

इसलिए यहाँ, यदि self == otherहम लौटते हैं True, तो हम NotImplementedवस्तु वापस करते हैं । यह ऑब्जेक्ट के किसी भी उपवर्ग के लिए डिफ़ॉल्ट व्यवहार है जो अपनी स्वयं की __eq__विधि को लागू नहीं करता है ।

कैसे __eq__कहा जाता है

फिर हम C API डॉक्स, PyObject_RichCompare फ़ंक्शन ढूंढते हैं, जो कॉल करता है do_richcompare

फिर हम देखते हैं कि C परिभाषा tp_richcompareके लिए बनाए गए फ़ंक्शन "object"को कहा जाता है 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__पहले प्रकार से विधि खोज सकते हैं और इसे कॉल कर सकते हैं। जब तक परिणाम NotImplemented नहीं होता है, अर्थात इसे लागू किया जाता है, हम इसे वापस करते हैं।

    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;

निष्कर्ष

एक तुलना में, हम पहले तुलना के उपवर्ग कार्यान्वयन का सम्मान करते हैं।

फिर हम पहली वस्तु के कार्यान्वयन के साथ तुलना करने का प्रयास करते हैं, फिर दूसरे के साथ अगर इसे नहीं बुलाया गया।

अंत में हम समानता के लिए तुलना के लिए पहचान के लिए एक परीक्षण का उपयोग करते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.