Python ฉันควรใช้__ne__()
ตัวดำเนินการตาม__eq__
?
คำตอบสั้น ๆ : อย่าใช้มัน แต่ถ้าคุณต้องใช้==
ไม่ใช่__eq__
ใน Python 3 !=
เป็นการปฏิเสธ==
โดยค่าเริ่มต้นดังนั้นคุณจึงไม่จำเป็นต้องเขียน a ด้วยซ้ำ__ne__
และเอกสารประกอบจะไม่มีความเห็นเกี่ยวกับการเขียนอีกต่อไป
โดยทั่วไปสำหรับโค้ด Python 3 เท่านั้นอย่าเขียนโค้ดเว้นแต่คุณจะต้องบดบังการใช้งานพาเรนต์เช่นสำหรับอ็อบเจ็กต์ในตัว
นั่นคือโปรดทราบความคิดเห็นของ Raymond Hettinger :
__ne__
วิธีการดังต่อไปนี้โดยอัตโนมัติจาก__eq__
เฉพาะในกรณีที่
__ne__
ไม่ได้กำหนดไว้แล้วใน superclass ดังนั้นหากคุณได้รับมรดกจาก builtin คุณควรแทนที่ทั้งสองอย่าง
หากคุณต้องการให้โค้ดของคุณทำงานใน Python 2 ให้ทำตามคำแนะนำสำหรับ Python 2 และมันจะทำงานใน Python 3 ได้ดี
ใน Python 2 Python เองไม่ได้ใช้การดำเนินการใด ๆ โดยอัตโนมัติในรูปแบบอื่นดังนั้นคุณควรกำหนด__ne__
เงื่อนไข==
แทนการใช้__eq__
. เช่น
class A(object):
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self == other
ดูหลักฐานว่า
- การใช้
__ne__()
ตัวดำเนินการตาม__eq__
และ
- ไม่ได้ใช้งาน
__ne__
ใน Python 2 เลย
แสดงพฤติกรรมที่ไม่ถูกต้องในการสาธิตด้านล่าง
คำตอบยาว
เอกสารสำหรับงูหลาม 2 พูดว่า:
ไม่มีความสัมพันธ์โดยนัยระหว่างตัวดำเนินการเปรียบเทียบ ความจริงของx==y
ไม่ได้หมายความว่าx!=y
เป็นเท็จ ดังนั้นเมื่อกำหนด__eq__()
ควรกำหนดด้วย__ne__()
เพื่อให้ตัวดำเนินการทำงานตามที่คาดไว้
นั่นหมายความว่าถ้าเรานิยาม__ne__
ในแง่ของการผกผัน__eq__
เราจะได้พฤติกรรมที่สอดคล้องกัน
ส่วนนี้ของเอกสารได้รับการอัปเดตสำหรับPython 3:
โดยค่าเริ่มต้น__ne__()
ได้รับมอบหมายให้และตีความผลเว้นแต่เป็น__eq__()
NotImplemented
และในส่วน"มีอะไรใหม่"เราพบว่าพฤติกรรมนี้เปลี่ยนไป:
!=
ตอนนี้ส่งกลับตรงข้ามของ==
เว้นแต่ผลตอบแทน==
NotImplemented
สำหรับการนำไปใช้งาน__ne__
เราต้องการใช้ตัว==
ดำเนินการแทนการใช้__eq__
วิธีการโดยตรงเพื่อที่ว่าหากself.__eq__(other)
คลาสย่อยส่งคืนNotImplemented
สำหรับประเภทที่ตรวจสอบ Python จะตรวจสอบอย่างเหมาะสมother.__eq__(self)
จากเอกสารประกอบ :
NotImplemented
วัตถุ
ประเภทนี้มีค่าเดียว มีวัตถุเดียวที่มีค่านี้ วัตถุนี้ถูกเข้าถึงผ่านชื่อใน
NotImplemented
ตัว เมธอดตัวเลขและวิธีการเปรียบเทียบที่สมบูรณ์อาจส่งคืนค่านี้หากไม่ได้ใช้การดำเนินการสำหรับตัวถูกดำเนินการที่ให้มา (จากนั้นล่ามจะลองใช้การดำเนินการที่สะท้อนหรือทางเลือกอื่น ๆ ขึ้นอยู่กับตัวดำเนินการ) ค่าความจริงเป็นจริง
เมื่อได้รับการดำเนินการเปรียบเทียบที่อุดมไปด้วยถ้าพวกเขาไม่ได้ชนิดเดียวกันตรวจสอบหลามถ้าother
เป็นชนิดย่อยและหากมีผู้ประกอบการที่กำหนดไว้จะใช้other
's วิธีแรก (ผกผันสำหรับ<
, <=
, >=
และ>
) ถ้าNotImplemented
ถูกส่งกลับก็จะใช้วิธีการของตรงกันข้าม (ไม่ได้ตรวจสอบวิธีเดิมซ้ำสองครั้ง) การใช้ตัว==
ดำเนินการทำให้ตรรกะนี้เกิดขึ้น
ความคาดหวัง
ตามความหมายคุณควรใช้__ne__
ในแง่ของการตรวจสอบความเท่าเทียมกันเนื่องจากผู้ใช้ในชั้นเรียนของคุณคาดหวังว่าฟังก์ชันต่อไปนี้จะเทียบเท่ากับ A:
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
อินสแตนซ์เหล่านี้การทดสอบภายใต้ Python 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
assert not wrong2 == wrong1
พฤติกรรมที่ไม่คาดคิด:
โปรดทราบว่าการเปรียบเทียบนี้ขัดแย้งกับการเปรียบเทียบด้านบน ( 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__
ใน Python 2
เพื่อเป็นหลักฐานว่าคุณไม่ควรข้ามการนำไปใช้__ne__
ใน Python 2 โปรดดูวัตถุที่เทียบเท่าเหล่านี้:
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child
True
ผลลัพธ์ข้างต้นควรเป็นFalse
!
ที่มา Python 3
การใช้ CPython เริ่มต้นสำหรับ__ne__
อยู่typeobject.c
ในobject_richcompare
:
case Py_NE:
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__
รายละเอียดการใช้งานเริ่มต้นของ Python 3 ที่ระดับ C ใช้__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
กำลังทำลอจิกใน Python ซึ่งจะได้รับการจัดการในระดับ C
ตอบสนองต่อนักวิจารณ์บางคน
ผู้ตอบอีกคนเขียนว่า:
การใช้งานวิธีnot self == other
การของAaron Hall __ne__
ไม่ถูกต้องเนื่องจากไม่สามารถย้อนกลับได้NotImplemented
( not NotImplemented
is False
) ดังนั้น__ne__
วิธีการที่มีลำดับความสำคัญจึงไม่สามารถถอยกลับไปใช้__ne__
วิธีการที่ไม่มีลำดับความสำคัญได้
การ__ne__
ไม่มีวันกลับมาNotImplemented
ไม่ได้ทำให้มันไม่ถูกต้อง แต่เราจัดการจัดลำดับความสำคัญด้วยNotImplemented
การตรวจสอบความเท่าเทียมกับ==
. สมมติว่า==
มีการใช้งานอย่างถูกต้องเราทำเสร็จแล้ว
not self == other
เคยเป็นการใช้งาน Python 3 เริ่มต้นของ__ne__
วิธีการนี้ แต่เป็นข้อผิดพลาดและได้รับการแก้ไขใน Python 3.4 ในเดือนมกราคม 2015 ตามที่ ShadowRanger สังเกตเห็น (ดูปัญหา # 21408)
เรามาอธิบายกันดีกว่า
ตามที่ระบุไว้ก่อนหน้านี้โดยค่าเริ่มต้น Python 3 จะจัดการ__ne__
โดยการตรวจสอบก่อนว่าself.__eq__(other)
ส่งคืนNotImplemented
(singleton) - ซึ่งควรตรวจสอบด้วยis
และส่งคืนหากเป็นเช่นนั้นมิฉะนั้นควรส่งคืนค่าผกผัน นี่คือตรรกะที่เขียนเป็นคลาส mixin:
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
สิ่งนี้จำเป็นสำหรับความถูกต้องสำหรับ Python API ระดับ C และได้รับการแนะนำใน Python 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__
โดยไม่คำนึงถึงเวอร์ชัน Python
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
>>> A() == B(), B() == A(), A() != B(), B() != A()
(False, False, True, True)
สรุป
สำหรับงูหลาม 2 รหัสที่รองรับการใช้งานในการดำเนินการ==
__ne__
มันเป็นมากกว่า:
ใน Python 3 เท่านั้นให้ใช้การปฏิเสธระดับต่ำในระดับ C ซึ่งง่ายกว่าและมีประสิทธิภาพมากขึ้น (แม้ว่าโปรแกรมเมอร์จะรับผิดชอบในการพิจารณาว่าถูกต้องก็ตาม)
อีกครั้งทำไม่ตรรกะระดับต่ำเขียนในระดับสูงหลาม
__ne__
โดยใช้__eq__
เพียงอย่างเดียวที่คุณนำไปใช้