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==y
anlamına gelmez x!=y
yanlış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 :
NotImplemented
nesne
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 other
bir 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 NotImplemented
dö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.c
iç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_python
Python'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 == other
ait __ne__
yönteme onu asla geri dönemez olarak yanlıştır NotImplemented
( not NotImplemented
edilmektedir 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 NotImplemented
yanlış yapmaz. Bunun yerine, ile NotImplemented
eşitlik kontrolü yoluyla önceliklendirmeyi ele alıyoruz ==
. ==
Doğru uygulandığını varsayarsak , işimiz bitti.
not self == other
eskiden __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 NotImplemented
ile kontrol edilmelidir - (a tek) is
eğ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 NotImplemented
olarak __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.