Python, __ne__()operatörü temel alarak uygulamalı __eq__mıyım?
Kısa Cevap: Gerekirse Do not, kullanım uygulamak, ama ==, değil__eq__
Python 3'te, varsayılan olarak !=olumsuzlamadır ==, bu nedenle a yazmanız bile gerekmez __ne__ve dokümantasyon artık bir tane yazma konusunda fikir sahibi değildir .
Genel olarak konuşursak, yalnızca Python 3 kodlu kod için, örneğin yerleşik bir nesne için üst uygulamayı gölgede bırakmanız gerekmedikçe bir tane yazmayın.
Yani, Raymond Hettinger'in yorumunu aklınızda bulundurun :
__ne__Yöntem otomatik takip __eq__eğer sadece
__ne__zaten Bir üst sınıfta tanımlanmış değildir. Yani, bir yerleşikten miras alıyorsanız, her ikisini de geçersiz kılmak en iyisidir.
Python 2'de çalışması için kodunuza ihtiyacınız varsa, Python 2 için öneriyi izleyin ve Python 3'te gayet iyi çalışacaktır.
Python 2'de, Python kendisi otomatik olarak başka açısından herhangi operasyonu uygulamıyor - bu nedenle, tanımlamalıdır __ne__açısından ==yerine __eq__. ÖRNEĞİN
class A(object):
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self == other
Kanıtı görün
- ve
__ne__()dayalı operatörü uygulamak__eq__
__ne__Python 2'de hiç uygulanmıyor
aşağıdaki gösterimde yanlış davranış sağlar.
Uzun cevap
Dokümantasyon Python 2 için diyor ki:
Karşılaştırma operatörleri arasında zımni ilişkiler yoktur. Gerçeği x==yanlamına gelmez x!=yyanlıştır. Buna göre, tanımlanırken __eq__(), __ne__()operatörlerin beklendiği gibi davranması için de tanımlanmalıdır .
Bu __ne__, tersi olarak tanımlarsak __eq__tutarlı davranışlar elde edebileceğimiz anlamına gelir .
Belgelerin bu bölümü Python 3 için güncellenmiştir :
Varsayılan olarak, __ne__()delege __eq__()olmadıkça sonucu tersine çevirir NotImplemented.
ve "yenilikler" bölümünde , bu davranışın değiştiğini görüyoruz:
!=Şimdi tersini döndürür ==sürece, ==getiri NotImplemented.
Uygulama için __ne__,==__eq__ yöntemi doğrudan kullanmak yerine operatörü kullanmayı tercih ederiz, böylece kontrol edilen tür için self.__eq__(other)bir alt sınıf dönerse NotImplemented, Python other.__eq__(self) belgelerden uygun şekilde kontrol eder :
NotImplementednesne
Bu türün tek bir değeri vardır. Bu değere sahip tek bir nesne var. Bu nesneye yerleşik ad aracılığıyla erişilir
NotImplemented. Sayısal yöntemler ve zengin karşılaştırma yöntemleri, sağlanan işlenenler için işlemi uygulamazlarsa bu değeri döndürebilir. (Tercüman daha sonra operatöre bağlı olarak yansıtılan işlemi veya başka bir geri dönüşü deneyecektir.) Doğruluk değeri doğrudur.
Onlar, Python çekler aynı tip değilseniz, zengin karşılaştırma operatörü verildiğinde ise otherbir alt tipi olduğunu ve tanımlanmış olduğu operatöre varsa, o kullanır other(için ters ilk 'ın metodunu <, <=, >=ve >). Eğer NotImplementeddöndürülür, daha sonra tam tersi bir yöntem kullanır. (O mu değil iki kez aynı yöntemle kontrol edin.) Kullanarak ==bu mantık gerçekleşmesi için operatör izin verir.
Beklentiler
Anlamsal olarak, __ne__eşitlik denetimi açısından uygulamalısınız çünkü sınıfınızın kullanıcıları aşağıdaki işlevlerin tüm A örnekleri için eşdeğer olmasını bekler:
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
Yani, yukarıdaki her iki işlev de her zaman aynı sonucu döndürmelidir. Ancak bu, programcıya bağlıdır.
Aşağıdakilere __ne__dayalı olarak tanımlarken beklenmeyen davranışın gösterilmesi __eq__:
İlk kurulum:
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."""
Eşdeğer olmayan örnekleri örnekleyin:
right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
Beklenen davranış:
(Not: Aşağıdakilerin her birinin her ikinci iddiası eşdeğerdir ve bu nedenle ondan öncekine mantıksal olarak gereksiz olsa da, biri diğerinin alt sınıfı olduğunda sıranın önemli olmadığını göstermek için onları dahil ediyorum . )
Bu örnekler aşağıdakilerle __ne__uygulanmıştır ==:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
Python 3 altında test edilen bu örnekler de doğru şekilde çalışır:
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
Ve bunların __ne__uygulandığını hatırlayın __eq__- bu beklenen davranış olsa da, uygulama yanlıştır:
assert not wrong1 == wrong2
assert not wrong2 == wrong1
Beklenmedik Davranış:
Bu karşılaştırmanın yukarıdaki karşılaştırmalarla çeliştiğini unutmayın ( not wrong1 == wrong2).
>>> assert wrong1 != wrong2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
ve,
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
__ne__Python 2'de atlamayın
__ne__Python 2'de uygulamayı atlamamanız gerektiğine dair kanıt için şu eşdeğer nesnelere bakın:
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child
True
Yukarıdaki sonuç şöyle olmalıdır False!
Python 3 kaynağı
İçin varsayılan CPython uygulama __ne__olduğu typeobject.ciçindeobject_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;
Ama varsayılan __ne__kullanır __eq__?
Python 3'ün __ne__C düzeyindeki varsayılan uygulama ayrıntısı __eq__, daha yüksek düzey ==( PyObject_RichCompare ) daha az verimli olacağı için kullanır - ve bu nedenle de işlemesi gerekir NotImplemented.
Doğru __eq__bir şekilde uygulanırsa, olumsuzlama ==da doğrudur - ve bizim düşük seviyeli uygulama ayrıntılarından kaçınmamızı sağlar __ne__.
Kullanımı ==, düşük seviyeli mantığımızı tek bir yerde tutmamızı ve adres vermekten kaçınmamızı sağlar .NotImplemented__ne__
Yanlışlıkla bunun ==geri dönebileceği varsayılabilir NotImplemented.
Aslında, __eq__kimliğini kontrol eden varsayılan uygulamasıyla aynı mantığı kullanır ( do_richcompare ve aşağıdaki kanıtımıza bakın)
class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
Ve karşılaştırmalar:
>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
Verim
Benim sözüme güvenmeyin, bakalım neyin daha performanslı olduğunu görelim:
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
Sanırım bu performans rakamları kendileri için konuşuyor:
>>> 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
Bu, low_level_pythonPython'da, aksi takdirde C düzeyinde ele alınacak bir mantık yaptığını düşündüğünüzde mantıklıdır .
Bazı eleştirmenlere yanıt
Başka bir cevaplayıcı yazıyor:
Aaron Hall'un uygulaması not self == otherait __ne__yönteme onu asla geri dönemez olarak yanlıştır NotImplemented( not NotImplementededilmektedir False) ve bu nedenle __ne__önceliği vardır yöntem geri düşebilir asla __ne__öncelik yoktur yöntemle.
__ne__Asla geri dönmemiş olmak NotImplementedyanlış yapmaz. Bunun yerine, ile NotImplementedeşitlik kontrolü yoluyla önceliklendirmeyi ele alıyoruz ==. ==Doğru uygulandığını varsayarsak , işimiz bitti.
not self == othereskiden __ne__yöntemin varsayılan Python 3 uygulamasıydı, ancak bir hataydı ve ShadowRanger'ın fark ettiği gibi Ocak 2015'te Python 3.4'te düzeltildi (bkz. sorun # 21408).
Peki, bunu açıklayalım.
Daha önce, Python 3 varsayılan tutamaçlarıyla belirtildiği gibi __ne__eğer ilk kontrol ederek self.__eq__(other)döner NotImplementedile kontrol edilmelidir - (a tek) iseğer öyleyse ve döndü aksi takdirde tersini dönmelidir. İşte sınıf karışımı olarak yazılan mantık:
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
Bu, C seviyesi Python API'sinin doğruluğu için gereklidir ve Python 3'te tanıtılmıştır.
gereksiz. __ne__Kendi kontrollerini uygulayanlar ve __eq__doğrudan veya aracılığıyla delege edenler de dahil olmak üzere ilgili tüm yöntemler kaldırıldı ==ve ==bunu yapmanın en yaygın yolu buydu.
Simetri Önemli mi?
Bizim kalıcı kritik taşıma bulunması için de bir patolojik örnek sağlar NotImplementedolarak __ne__, her şeyden önce simetri değerlemesi. Açık bir örnekle tartışmayı çelik adam edelim:
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)
Dolayısıyla, bu mantıkla, simetriyi korumak için __ne__, Python sürümünden bağımsız olarak karmaşık olanı yazmamız gerekir .
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)
Görünüşe göre bu örneklerin hem eşit hem de eşit olmadığı konusunda hiçbir fikrimiz yok.
Simetrinin, mantıklı kod varsayımından ve dokümantasyondaki tavsiyeleri takip etmekten daha az önemli olduğunu öneriyorum.
Bununla birlikte, eğer A'nın mantıklı bir uygulaması olsaydı __eq__, o zaman burada hala benim yönümü izleyebilirdik ve yine de simetriye sahip olurduk:
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)
Sonuç
Python 2 uyumlu kod ==için uygulamak için kullanın __ne__. Bu daha fazla:
Yalnızca Python 3'te, C düzeyindeki düşük düzey olumsuzlamayı kullanın - daha da basit ve performanslıdır (ancak programcı doğru olup olmadığını belirlemekten sorumludur ).
Yine, do not üst düzey Python yazma düşük seviyeli mantık.
__ne__kullanarak__eq__bunu uygulamak sadece o.