Bij het schrijven van aangepaste klassen is het vaak belangrijk om gelijkwaardigheid toe te staan door middel van de operators ==
en !=
. In Python wordt dit mogelijk gemaakt door respectievelijk de speciale methoden __eq__
en __ne__
te implementeren. De gemakkelijkste manier die ik heb gevonden om dit te doen, is de volgende methode:
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
Kent u een elegantere manier om dit te doen? Kent u bepaalde nadelen van het gebruik van de bovenstaande methode voor het vergelijken van __dict__
s?
Opmerking: een beetje verduidelijking: als __eq__
en __ne__
niet gedefinieerd zijn, ziet u dit gedrag:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
Dat wil zeggen, a == b
evalueert tot False
omdat het echt a is b
uitvoert, een identiteitstest (dwz ” Is a
hetzelfde object als B
?”).
Als __eq__
en __ne__
zijn gedefinieerd, zie je dit gedrag (dat is degene die we zoeken):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
Antwoord 1, autoriteit 100%
Beschouw dit eenvoudige probleem:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
Dus, Python gebruikt standaard de object-ID’s voor vergelijkingsbewerkingen:
id(n1) # 140400634555856
id(n2) # 140400634555920
Het negeren van de functie __eq__
lijkt het probleem op te lossen:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
Onthoud in Python 2altijd dat u ook de functie __ne__
overschrijft, evenals de documentatiestelt:
Er zijn geen impliciete relaties tussen de vergelijkingsoperatoren. De
waarheid vanx==y
impliceert niet datx!=y
onwaar is. Dienovereenkomstig, wanneer?
het definiëren van__eq__()
, moet men ook__ne__()
definiëren zodat de
operators zullen zich gedragen zoals verwacht.
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
In Python 3is dit niet langer nodig, omdat de documentatiestelt:
Standaard delegeert
__ne__()
naar__eq__()
en keert het resultaat om
tenzij hetNotImplemented
is. Er zijn geen andere geïmpliceerde
relaties tussen de vergelijkingsoperatoren, bijvoorbeeld de waarheid
van(x<y or x==y)
betekent nietx<=y
.
Maar dat lost niet al onze problemen op. Laten we een subklasse toevoegen:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
Opmerking:Python 2 heeft twee soorten klassen:
-
klassiek- style(of oude stijl) klassen, die nieterven van
object
en die worden gedeclareerd alsclass A:
,class A():
ofclass A(B):
waarbijB
een klassieke stijl is klas; -
nieuw- styleklassen, die erven van
object
en die worden gedeclareerd alsclass A(object)
ofclass A(B):
waarbijB
een klasse nieuwe stijl is. Python 3 heeft alleen klassen nieuwe stijl die zijn gedeclareerd alsclass A:
,class A(object):
ofclass A(B):
.
Voor klassen in klassieke stijl roept een vergelijkingsbewerking altijd de methode van de eerste operand aan, terwijl voor klassen nieuwe stijl altijd de methode van de operand van de subklasse wordt aangeroepen, ongeacht de volgorde van de operanden.
Dus hier, als Number
een klas in klassieke stijl is:
n1 == n3
roeptn1.__eq__
aan;n3 == n1
roeptn3.__eq__
aan;n1 != n3
roeptn1.__ne__
aan;n3 != n1
roeptn3.__ne__
aan.
En als Number
een klasse nieuwe stijl is:
- zowel
n1 == n3
alsn3 == n1
bellenn3.__eq__
; - zowel
n1 != n3
alsn3 != n1
bellenn3.__ne__
.
Om het niet-commutativiteitsprobleem van de operators ==
en !=
voor de klassieke klassen van Python 2 op te lossen, worden de __eq__
en __ne__
-methoden moeten de waarde NotImplemented
retourneren als een operandtype niet wordt ondersteund. De documentatiedefinieert de NotImplemented
waarde als:
Numerieke methoden en uitgebreide vergelijkingsmethoden kunnen deze waarde retourneren als
ze implementeren de bewerking niet voor de opgegeven operanden. (De
interpreter zal dan de gereflecteerde bewerking proberen, of een andere
fallback, afhankelijk van de operator.) De waarheidswaarde is waar.
In dit geval delegeert de operator de vergelijkingsbewerking aan de gereflecteerde methodevan de andereoperand. De documentatiedefinieert gereflecteerde methoden als:
Er zijn geen versies met verwisselde argumenten van deze methoden (te gebruiken)
wanneer het linkerargument de bewerking niet ondersteunt, maar het rechterargument
argument doet); in plaats daarvan zijn__lt__()
en__gt__()
elkaars
reflectie,__le__()
en__ge__()
zijn elkaars reflectie, en
__eq__()
en__ne__()
zijn hun eigen reflectie.
Het resultaat ziet er als volgt uit:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
Het retourneren van de waarde NotImplemented
in plaats van False
is de juiste keuze, zelfs voor klassen nieuwe stijl als commutativiteitvan de ==
en !=
operators is gewenst wanneer de operanden van niet-gerelateerde typen zijn (geen overerving).
Zijn we er al? Niet helemaal. Hoeveel unieke nummers hebben we?
len(set([n1, n2, n3])) # 3 -- oops
Sets gebruiken de hashes van objecten, en standaard retourneert Python de hash van de identifier van het object. Laten we proberen het te negeren:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
Het eindresultaat ziet er als volgt uit (ik heb aan het eind enkele beweringen toegevoegd ter validatie):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2
Antwoord 2, autoriteit 54%
Je moet voorzichtig zijn met overerving:
>>> class Foo:
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
>>> class Bar(Foo):pass
>>> b = Bar()
>>> f = Foo()
>>> f == b
True
>>> b == f
False
Controleer typen strenger, zoals deze:
def __eq__(self, other):
if type(other) is type(self):
return self.__dict__ == other.__dict__
return False
Bovendien zal je aanpak prima werken, daar zijn speciale methoden voor.
Antwoord 3, autoriteit 39%
De manier die je beschrijft, is de manier waarop ik het altijd heb gedaan. Omdat het volledig generiek is, kun je die functionaliteit altijd opsplitsen in een mixin-klasse en deze overnemen in klassen waar je die functionaliteit wilt.
class CommonEqualityMixin(object):
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
class Foo(CommonEqualityMixin):
def __init__(self, item):
self.item = item
Antwoord 4, autoriteit 4%
Geen direct antwoord, maar leek relevant genoeg om aan te pakken, omdat het af en toe een beetje uitgebreide verveling bespaart. Rechtstreeks uit de documenten knippen…
Gegeven een klasse die een of meer uitgebreide vergelijkingsmethodes definieert, levert deze klassedecorateur de rest.Dit vereenvoudigt de inspanning die nodig is om alle mogelijke uitgebreide vergelijkingsbewerkingen te specificeren:
De klasse moet een van __lt__()
, __le__()
, __gt__()
of __ge__()
. Daarnaast moet de klasse een __eq__()
methode leveren.
Nieuw in versie 2.7
@total_ordering
class Student:
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
Antwoord 5, autoriteit 2%
U hoeft niet zowel __eq__
als __ne__
te overschrijven, u kunt alleen __cmp__
overschrijven, maar dit heeft gevolgen voor het resultaat van ==, !==, < , > enzovoort.
is
tests voor objectidentiteit. Dit betekent dat a is
b True
zal zijn in het geval dat a en b beide de verwijzing naar hetzelfde object bevatten. In python heb je altijd een verwijzing naar een object in een variabele, niet het eigenlijke object, dus in wezen voor a is b om waar te zijn, moeten de objecten erin zich op dezelfde geheugenlocatie bevinden. Hoe en vooral waarom zou je dit gedrag negeren?
Bewerken: ik wist niet dat __cmp__
uit python 3 was verwijderd, dus vermijd het.
Antwoord 6
Van dit antwoord: https://stackoverflow.com/a/30676267/541136heb ik aangetoond dat, terwijl het is correct om __ne__
te definiëren in termen __eq__
– in plaats van
def __ne__(self, other):
return not self.__eq__(other)
je moet gebruiken:
def __ne__(self, other):
return not self == other
Antwoord 7
Ik denk dat de twee termen die u zoekt gelijkheid(==) en identiteit(is) zijn. Bijvoorbeeld:
>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True <-- a and b have values which are equal
>>> a is b
False <-- a and b are not the same list object
Antwoord 8
De ‘is’-test zal de identiteit testen met behulp van de ingebouwde ‘id()’-functie die in wezen het geheugenadres van het object retourneert en daarom niet overbelastbaar is.
In het geval van het testen van de gelijkheid van een klasse, wil je waarschijnlijk wat strenger zijn in je tests en alleen de gegevensattributen in je klasse vergelijken:
import types
class ComparesNicely(object):
def __eq__(self, other):
for key, value in self.__dict__.iteritems():
if (isinstance(value, types.FunctionType) or
key.startswith("__")):
continue
if key not in other.__dict__:
return False
if other.__dict__[key] != value:
return False
return True
Deze code vergelijkt alleen niet-functionele gegevensleden van uw klasse en slaat ook alles over wat privé is wat u over het algemeen wilt. In het geval van gewone oude Python-objecten heb ik een basisklasse die __init__, __str__, __repr__ en __eq__ implementeert, zodat mijn POPO-objecten niet de last dragen van al die extra (en in de meeste gevallen identieke) logica.
Antwoord 9
In plaats van subklassen/mixins te gebruiken, gebruik ik graag een generieke klassedecorateur
def comparable(cls):
""" Class decorator providing generic comparison functionality """
def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
def __ne__(self, other):
return not self.__eq__(other)
cls.__eq__ = __eq__
cls.__ne__ = __ne__
return cls
Gebruik:
@comparable
class Number(object):
def __init__(self, x):
self.x = x
a = Number(1)
b = Number(1)
assert a == b
Antwoord 10
Dit omvat de opmerkingen over het antwoord van Algorias en vergelijkt objecten met een enkel kenmerk, omdat het hele dict me niet interesseert. hasattr(other, "id")
moet waar zijn, maar ik weet dat dit komt omdat ik het in de constructor heb ingesteld.
def __eq__(self, other):
if other is self:
return True
if type(other) is not type(self):
# delegate to superclass
return NotImplemented
return other.id == self.id
Antwoord 11
Ik heb een aangepaste basis geschreven met een standaardimplementatie van __ne__
die eenvoudig __eq__
negeert:
class HasEq(object):
"""
Mixin that provides a default implementation of ``object.__neq__`` using the subclass's implementation of ``object.__eq__``.
This overcomes Python's deficiency of ``==`` and ``!=`` not being symmetric when overloading comparison operators
(i.e. ``not x == y`` *does not* imply that ``x != y``), so whenever you implement
`object.__eq__ <https://docs.python.org/2/reference/datamodel.html#object.__eq__>`_, it is expected that you
also implement `object.__ne__ <https://docs.python.org/2/reference/datamodel.html#object.__ne__>`_
NOTE: in Python 3+ this is no longer necessary (see https://docs.python.org/3/reference/datamodel.html#object.__ne__)
"""
def __ne__(self, other):
"""
Default implementation of ``object.__ne__(self, other)``, delegating to ``self.__eq__(self, other)``.
When overriding ``object.__eq__`` in Python, one should also override ``object.__ne__`` to ensure that
``not x == y`` is the same as ``x != y``
(see `object.__eq__ <https://docs.python.org/2/reference/datamodel.html#object.__eq__>`_ spec)
:return: ``NotImplemented`` if ``self.__eq__(other)`` returns ``NotImplemented``, otherwise ``not self.__eq__(other)``
"""
equal = self.__eq__(other)
# the above result could be either True, False, or NotImplemented
if equal is NotImplemented:
return NotImplemented
return not equal
Als u van deze basisklasse erft, hoeft u alleen __eq__
en de basis te implementeren.
Achteraf gezien was het misschien beter geweest om het in plaats daarvan als decorateur te implementeren. Iets als @functools.total_ordering