अजगर, क्या मुझे __ne__()
ऑपरेटर के आधार पर लागू करना चाहिए __eq__
?
संक्षिप्त उत्तर: इसे लागू न करें, लेकिन यदि आपको इसका उपयोग करना है, तो ==
नहीं__eq__
पायथन 3 में, डिफ़ॉल्ट रूप !=
से निषेध है ==
, इसलिए आपको लिखने के लिए भी आवश्यक नहीं है __ne__
, और दस्तावेज़ को अब एक लिखने पर राय नहीं दी गई है।
आम तौर पर, पायथन 3-केवल कोड के लिए, एक को तब तक न लिखें जब तक आपको मूल कार्यान्वयन के लिए ओवरडोज़ करने की आवश्यकता न हो, जैसे कि एक बेसिन ऑब्जेक्ट के लिए।
यही है, रेमंड हेटिंगर की टिप्पणी को ध्यान में रखें :
यदि कोई सुपरक्लॉस में पहले से ही परिभाषित नहीं है, तो यह __ne__
विधि स्वचालित रूप से __eq__
केवल
इस प्रकार __ne__
है। इसलिए, यदि आप एक बिलिन से विरासत में मिले हैं, तो दोनों को ओवरराइड करना सबसे अच्छा है।
यदि आपको Python 2 में काम करने के लिए अपने कोड की आवश्यकता है, तो Python 2 की अनुशंसा का पालन करें और यह Python 3 में ठीक काम करेगा।
पायथन 2 में, पायथन खुद ही किसी अन्य के संदर्भ में किसी भी ऑपरेशन को स्वचालित रूप से लागू नहीं करता है - इसलिए, आपको इसके बजाय __ne__
में परिभाषित करना चाहिए । ईजी==
__eq__
class A(object):
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self == other # NOT `return not self.__eq__(other)`
सबूत देखिए कि
- लागू करने वाला
__ne__()
ऑपरेटर __eq__
और उसके आधार पर
__ne__
पायथन 2 में बिल्कुल भी लागू नहीं होना
नीचे प्रदर्शन में गलत व्यवहार प्रदान करता है।
लंबा जवाब
प्रलेखन अजगर 2 के लिए कहते हैं:
तुलना ऑपरेटरों के बीच कोई निहित संबंध नहीं हैं। सत्य का x==y
अर्थ x!=y
असत्य नहीं है । तदनुसार, परिभाषित करते समय __eq__()
, एक को भी परिभाषित करना चाहिए __ne__()
ताकि ऑपरेटर अपेक्षा के अनुरूप व्यवहार करेंगे।
तो इसका मतलब है कि अगर हम __ne__
उलटा के संदर्भ में परिभाषित करते हैं __eq__
, तो हम लगातार व्यवहार प्राप्त कर सकते हैं।
प्रलेखन का यह खंड पायथन 3 के लिए अद्यतन किया गया है :
डिफ़ॉल्ट रूप से, जब तक यह नहीं होता है तब तक परिणाम को दर्शाता __ne__()
है __eq__()
और इनवर्ट करता है NotImplemented
।
और "नया क्या है" अनुभाग में , हम देखते हैं कि यह व्यवहार बदल गया है:
!=
अब ==
जब तक नहीं ==
लौटाता है , के विपरीत है NotImplemented
।
लागू करने के लिए __ne__
, हम==
__eq__
सीधे विधि का उपयोग करने के बजाय ऑपरेटर का उपयोग करना पसंद करते हैं ताकि यदि self.__eq__(other)
कोई उप- NotImplemented
प्रकार की जाँच के लिए वापस आए , तो पायथन उचित रूप other.__eq__(self)
से दस्तावेज़ से जाँच करेगा :
NotImplemented
वस्तु
इस प्रकार का एकल मान है। इस मान के साथ एक एकल ऑब्जेक्ट है। यह ऑब्जेक्ट बिल्ट-इन नाम से एक्सेस किया गया है
NotImplemented
। यदि वे प्रदान किए गए ऑपरेंड के लिए संचालन को कार्यान्वित नहीं करते हैं तो संख्यात्मक विधियाँ और समृद्ध तुलना विधियाँ इस मान को लौटा सकती हैं। (दुभाषिया फिर ऑपरेटर के आधार पर परिलक्षित ऑपरेशन, या कुछ अन्य कमियां की कोशिश करेगा।) इसका सत्य मूल्य सत्य है।
जब एक अमीर तुलना ऑपरेटर दिया है, अगर वे एक ही प्रकार, नहीं कर रहे हैं अजगर जांच करता है कि other
एक उप-प्रकार है, और अगर यह है कि ऑपरेटर परिभाषित है, यह का उपयोग करता है other
के विधि पहले (उलटा के लिए <
, <=
, >=
और >
)। यदि NotImplemented
लौटाया जाता है, तो यह विपरीत विधि का उपयोग करता है। (यह एक ही विधि के लिए दो बार जांच नहीं करता है ।) ==
ऑपरेटर का उपयोग करने से इस तर्क के लिए जगह मिलती है।
उम्मीदें
शब्दार्थ, आपको __ne__
समानता के लिए चेक के संदर्भ में लागू करना चाहिए क्योंकि आपकी कक्षा के उपयोगकर्ता ए के सभी उदाहरणों के लिए निम्नलिखित कार्यों की अपेक्षा करेंगे।
def negation_of_equals(inst1, inst2):
"""always should return same as not_equals(inst1, inst2)"""
return not inst1 == inst2
def not_equals(inst1, inst2):
"""always should return same as negation_of_equals(inst1, inst2)"""
return inst1 != inst2
यही है, उपरोक्त दोनों कार्यों को हमेशा एक ही परिणाम देना चाहिए । लेकिन यह प्रोग्रामर पर निर्भर है।
जब परिभाषित करने __ne__
पर अप्रत्याशित व्यवहार का प्रदर्शन __eq__
:
पहला सेटअप:
class BaseEquatable(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
return isinstance(other, BaseEquatable) and self.x == other.x
class ComparableWrong(BaseEquatable):
def __ne__(self, other):
return not self.__eq__(other)
class ComparableRight(BaseEquatable):
def __ne__(self, other):
return not self == other
class EqMixin(object):
def __eq__(self, other):
"""override Base __eq__ & bounce to other for __eq__, e.g.
if issubclass(type(self), type(other)): # True in this example
"""
return NotImplemented
class ChildComparableWrong(EqMixin, ComparableWrong):
"""__ne__ the wrong way (__eq__ directly)"""
class ChildComparableRight(EqMixin, ComparableRight):
"""__ne__ the right way (uses ==)"""
class ChildComparablePy3(EqMixin, BaseEquatable):
"""No __ne__, only right in Python 3."""
तत्काल गैर-समकक्ष उदाहरण:
right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
अपेक्षित् व्यवहार:
(ध्यान दें: जबकि नीचे के प्रत्येक के प्रत्येक दूसरे दावे के बराबर है और इसलिए तार्किक रूप से इससे पहले कि यह अनावश्यक है, मैं उन्हें प्रदर्शित करने के लिए शामिल हूं कि जब कोई दूसरे का उपवर्ग हो तो यह कोई फर्क नहीं पड़ता। )
इन उदाहरणों के __ne__
साथ लागू किया गया है ==
:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
ये उदाहरण, पायथन 3 के तहत परीक्षण, भी सही ढंग से काम करते हैं:
assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1
और याद रखें कि ये __ne__
लागू हो चुके हैं __eq__
- जबकि यह अपेक्षित व्यवहार है, कार्यान्वयन गलत है:
assert not wrong1 == wrong2 # These are contradicted by the
assert not wrong2 == wrong1 # below unexpected behavior!
अप्रत्याशित व्यवहार:
ध्यान दें कि यह तुलना ऊपर ( not wrong1 == wrong2
) से तुलना करती है ।
>>> assert wrong1 != wrong2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
तथा,
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
__ne__
अजगर 2 में छोड़ें नहीं
सबूत के लिए कि आपको __ne__
पायथन 2 में कार्यान्वयन को छोड़ना नहीं चाहिए , इन समकक्ष वस्तुओं को देखें:
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True
उपरोक्त परिणाम होना चाहिए False
!
पायथन 3 स्रोत
के लिए डिफ़ॉल्ट CPython कार्यान्वयन __ne__
में है typeobject.c
मेंobject_richcompare
:
case Py_NE:
/* By default, __ne__() delegates to __eq__() and inverts the result,
unless the latter returns NotImplemented. */
if (Py_TYPE(self)->tp_richcompare == NULL) {
res = Py_NotImplemented;
Py_INCREF(res);
break;
}
res = (*Py_TYPE(self)->tp_richcompare)(self, other, Py_EQ);
if (res != NULL && res != Py_NotImplemented) {
int ok = PyObject_IsTrue(res);
Py_DECREF(res);
if (ok < 0)
res = NULL;
else {
if (ok)
res = Py_False;
else
res = Py_True;
Py_INCREF(res);
}
}
break;
लेकिन डिफ़ॉल्ट __ne__
उपयोग करता है __eq__
?
__ne__
सी स्तर पर पायथन 3 का डिफ़ॉल्ट कार्यान्वयन विवरण __eq__
उच्च स्तर ==
( PyObject_RichCompare ) का उपयोग कम कुशल होगा - और इसलिए इसे भी संभालना होगा NotImplemented
।
यदि __eq__
इसे सही तरीके से लागू किया जाता है, तो इसका निषेध ==
भी सही है - और यह हमें अपने में निम्न स्तर के कार्यान्वयन विवरण से बचने की अनुमति देता है __ne__
।
उपयोग ==
करने से हम अपने निम्न स्तर के तर्क को एक स्थान पर रख सकते हैं , और संबोधित करने से बच सकतेNotImplemented
हैं __ne__
।
कोई गलत तरीके से मान सकता है कि ==
वापस आ सकता है NotImplemented
।
यह वास्तव में डिफ़ॉल्ट कार्यान्वयन के रूप में एक ही तर्क का उपयोग करता है __eq__
, जो पहचान के लिए जाँच करता है (नीचे do_richcompare और हमारे साक्ष्य देखें)
class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
और तुलना:
>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
प्रदर्शन
इसके लिए मेरा शब्द न लें, आइए देखें कि अधिक प्रदर्शन करने वाला क्या है:
class CLevel:
"Use default logic programmed in C"
class HighLevelPython:
def __ne__(self, other):
return not self == other
class LowLevelPython:
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
def c_level():
cl = CLevel()
return lambda: cl != cl
def high_level_python():
hlp = HighLevelPython()
return lambda: hlp != hlp
def low_level_python():
llp = LowLevelPython()
return lambda: llp != llp
मुझे लगता है कि ये प्रदर्शन संख्याएं खुद के लिए बोलती हैं:
>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029
यह समझ में आता है जब आप समझते हैं कि low_level_python
पायथन में तर्क है कि अन्यथा सी स्तर पर संभाला जाएगा।
कुछ आलोचकों का जवाब
एक अन्य उत्तरदाता लिखते हैं:
हारून हॉल के कार्यान्वयन not self == other
की __ne__
विधि सही नहीं है के रूप में यह कभी नहीं लौट सकते हैं NotImplemented
( not NotImplemented
है False
) और इसलिए __ne__
विधि प्राथमिकता है कि पर वापस गिर कभी नहीं हो सकता __ne__
विधि है कि प्राथमिकता नहीं है।
बीत रहा है __ne__
कभी नहीं बदले NotImplemented
यह गलत नहीं है। इसके बजाय, हम NotImplemented
समानता के लिए चेक के माध्यम से प्राथमिकता को संभालते हैं ==
। मान लिया गया ==
कि सही तरीके से लागू किया गया है, तो हम कर रहे हैं।
not self == other
डिफ़ॉल्ट पायथन 3 __ne__
पद्धति का कार्यान्वयन हुआ करता था, लेकिन यह एक बग था और इसे जनवरी 2015 को पायथन 3.4 में सही कर दिया गया था, क्योंकि ShadowRanger पर ध्यान दिया गया था (# 21408 अंक देखें)।
खैर, यह समझाते हैं।
जैसा कि पहले उल्लेख किया गया है, __ne__
अगर self.__eq__(other)
रिटर्न NotImplemented
(एक सिंगलटन) - पहले चेक द्वारा डिफ़ॉल्ट रूप से पायथन 3 - जिसके साथ जांच की जानी चाहिए is
और यदि ऐसा है, तो वापस लौटना चाहिए। यहाँ उस तर्क को एक वर्ग मिश्रण के रूप में लिखा गया है:
class CStyle__ne__:
"""Mixin that provides __ne__ functionality equivalent to
the builtin functionality
"""
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
यह सी स्तर पायथन एपीआई के लिए शुद्धता के लिए आवश्यक है, और इसे पायथन 3 में पेश किया गया था
अनावश्यक। सभी संबंधित __ne__
तरीकों को हटा दिया गया था, जिसमें स्वयं के चेक को लागू करने वाले भी शामिल थे जो __eq__
सीधे या उसके माध्यम से प्रतिनिधि ==
- और ==
ऐसा करने का सबसे आम तरीका था।
समरूपता महत्वपूर्ण है?
हमारे लगातार आलोचक से निपटने के लिए मामला बनाने के लिए एक रोग उदाहरण प्रदान करता है NotImplemented
में __ne__
, सभी के ऊपर समरूपता मूल्य। आइए स्टील-मैन को एक स्पष्ट उदाहरण के साथ तर्क दें:
class B:
"""
this class has no __eq__ implementation, but asserts
any instance is not equal to any other object
"""
def __ne__(self, other):
return True
class A:
"This class asserts instances are equivalent to all other objects"
def __eq__(self, other):
return True
>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, False, True)
इसलिए, इस तर्क से, समरूपता बनाए रखने के लिए, हमें __ne__
पाइथन संस्करण की परवाह किए बिना, जटिल लिखना होगा ।
class B:
def __ne__(self, other):
return True
class A:
def __eq__(self, other):
return True
def __ne__(self, other):
result = other.__eq__(self)
if result is NotImplemented:
return NotImplemented
return not result
>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, True, True)
स्पष्ट रूप से हमें इस बात पर कोई ध्यान नहीं देना चाहिए कि ये उदाहरण दोनों समान हैं और समान नहीं हैं।
मेरा प्रस्ताव है कि समरूपता समझदार कोड के अनुमान और प्रलेखन की सलाह का पालन करने से कम महत्वपूर्ण है।
हालाँकि, यदि A का एक समझदार क्रियान्वयन होता __eq__
, तो हम अभी भी यहाँ अपनी दिशा का पालन कर सकते थे और हमारे पास अभी भी समरूपता होगी:
class B:
def __ne__(self, other):
return True
class A:
def __eq__(self, other):
return False # <- this boolean changed...
>>> A() == B(), B() == A(), A() != B(), B() != A()
(False, False, True, True)
निष्कर्ष
पायथन 2 संगत कोड के लिए, ==
कार्यान्वित करने के लिए उपयोग करें __ne__
। यह अधिक है:
केवल पायथन 3 में, सी स्तर पर निम्न-स्तर की उपेक्षा का उपयोग करें - यह और भी अधिक सरल और प्रदर्शनकारी है (हालांकि प्रोग्रामर यह निर्धारित करने के लिए ज़िम्मेदार है कि यह सही है )।
फिर से, उच्च स्तर के पायथन में निम्न-स्तरीय तर्क न लिखें।
__ne__
__eq__