Eşitlik için nesne örneklerini özelliklerine göre karşılaştırın


244

MyClassİki üye değişkenleri içeren bir sınıf var foove bar:

class MyClass:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

Bu sınıfın her biri için aynı değerleri olan iki örneği var foove bar:

x = MyClass('foo', 'bar')
y = MyClass('foo', 'bar')

Ancak, onları eşitlik için karşılaştırdığımda, Python döndürür False:

>>> x == y
False

Python'un bu iki nesneyi eşit görmesini nasıl sağlayabilirim?

Yanıtlar:


354

Yöntemi uygulamalısınız __eq__:

class MyClass:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    def __eq__(self, other): 
        if not isinstance(other, MyClass):
            # don't attempt to compare against unrelated types
            return NotImplemented

        return self.foo == other.foo and self.bar == other.bar

Şimdi çıktılar:

>>> x == y
True

Uygulamanın __eq__sınıfınızın örneklerini otomatik olarak paylaşılamaz hale getireceğini unutmayın, bu da kümelerde ve diktelerde saklanamayacakları anlamına gelir. Değiştirilemez bir türü modellemiyorsanız (yani, öznitelikler foove barnesnenizin ömrü boyunca değer değiştirebilirse), örneklerinizi paylaşılmaz olarak bırakmanız önerilir.

Değişmez bir tipi modelliyorsanız, veri modeli kancasını da uygulamalısınız __hash__:

class MyClass:
    ...

    def __hash__(self):
        # necessary for instances to behave sanely in dicts and sets.
        return hash((self.foo, self.bar))

__dict__Değerler arasında geçiş yapma ve karşılaştırma fikri gibi genel bir çözüm tavsiye edilmez - hiçbir zaman gerçekten genel olamaz çünkü __dict__içinde karşılaştırılabilir veya paylaşılamaz türler bulunabilir.

Not: Python 3'ten önce __cmp__yerine kullanmanız gerekebileceğini unutmayın __eq__. Python 2 kullanıcıları da uygulamak isteyebilir __ne__, çünkü eşitsizlik için mantıklı bir varsayılan davranış (yani eşitlik sonucunu tersine çevirmek) Python 2'de otomatik olarak oluşturulmayacaktır.


2
return NotImplemented(Yükseltmek yerine NotImplementedError) kullanımını merak ettim . Bu konu burada ele alınmıştır: stackoverflow.com/questions/878943/…
init_js

48

Nesnenizdeki zengin karşılaştırma işleçlerini geçersiz kılarsınız.

class MyClass:
 def __lt__(self, other):
      # return comparison
 def __le__(self, other):
      # return comparison
 def __eq__(self, other):
      # return comparison
 def __ne__(self, other):
      # return comparison
 def __gt__(self, other):
      # return comparison
 def __ge__(self, other):
      # return comparison

Bunun gibi:

    def __eq__(self, other):
        return self._id == other._id

3
Python 2,5 ve sonrası, sınıf tanımlamak gerektiğine dikkat ediniz __eq__(), ama yalnızca bir __lt__(), __le__(), __gt__()ya da __ge__()buna ek olarak gereklidir. Bundan Python diğer yöntemleri çıkarabilir. Daha functoolsfazla bilgi için bakınız .
kba

1
@kba, bunun doğru olduğunu düşünmüyorum. Bu işe yarayabilir functoolsmodül, ancak çalışmıyor standart komparatörlerinin için: MyObj1 != Myobj2sadece olacak işi eğer __ne__()yöntem uygulanmaktadır.
Arel

6
functools hakkında özel ipucu, @functools.total_orderingdekoratörü sınıfınızda kullanmak olmalıdır , o zaman yukarıdaki gibi sadece __eq__ve bir tane tanımlayabilirsiniz ve geri kalanı türetilir
Anentropic

7

Uygulamak __eq__sınıfınızda yöntemi; böyle bir şey:

def __eq__(self, other):
    return self.path == other.path and self.title == other.title

Düzenleme: nesnelerinizin eşit karşılaştırmasını istiyorsanız ve yalnızca eşit örnek sözlükleri varsa:

def __eq__(self, other):
    return self.__dict__ == other.__dict__

Belki de self is otheraynı nesne olup olmadıklarını görmek istersiniz .
S.Lott

2
-1. Bu iki sözlük örneği olsa bile, Python bunları anahtar / değerlerle otomatik olarak karşılaştıracaktır. Bu Java değil ...
e-satis

İlk çözüm bir yükseltebilir AttributeError. Satırı eklemelisiniz if hasattr(other, "path") and hasattr(other, "title"):( Python belgelerindeki bu güzel örnek gibi ).
Maggyero

5

Özet olarak:

  1. <= 2.0 python çalıştırmanız ( 2.1'de eklenmişse) dışında, __eq__bunun yerine uygulanması önerilir.__cmp____eq__
  2. Uygulamayı da unutmayın __ne__( çok özel bir durum return not self.__eq__(other)veya benzeri bir şey olmalıdır return not self == other)
  3. Operatörün karşılaştırmak istediğiniz her özel sınıfa uygulanması gerektiğini unutmayın (aşağıdaki örneğe bakın).
  4. Yok olabilecek bir nesneyle karşılaştırmak istiyorsanız, nesneyi uygulamalısınız. Tercüman tahmin edemez ... (aşağıdaki örneğe bakın)

    class B(object):
      def __init__(self):
        self.name = "toto"
      def __eq__(self, other):
        if other is None:
          return False
        return self.name == other.name
    
    class A(object):
      def __init__(self):
        self.toto = "titi"
        self.b_inst = B()
      def __eq__(self, other):
        if other is None:
          return False
        return (self.toto, self.b_inst) == (other.toto, other.b_inst)
    

2

Özel durumunuza bağlı olarak şunları yapabilirsiniz:

>>> vars(x) == vars(y)
True

Bir nesnenin alanlarındaki Python sözlüğüne bakın


Ayrıca ilginç, vars bir dict döndürürken, görsel inceleme aslında eşit olduklarını gösterse de, unittest'in assertDictEqual'i işe yaramıyor gibi görünüyor. Bu dicts dizeleri çevirerek ve bunları karşılaştırarak var: self.assertEqual (str (vars (tbl0)), str (vars (local_tbl0)))
Ben

2

Python 3.7'deki (ve üzeri) Veri Kağıtları ile , eşitlik için nesne örneklerinin karşılaştırılması yerleşik bir özelliktir.

Python 3.6 için Dataclasses için bir backport mevcuttur.

(Py37) nsc@nsc-vbox:~$ python
Python 3.7.5 (default, Nov  7 2019, 10:50:52) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dataclasses import dataclass
>>> @dataclass
... class MyClass():
...     foo: str
...     bar: str
... 
>>> x = MyClass(foo="foo", bar="bar")
>>> y = MyClass(foo="foo", bar="bar")
>>> x == y
True

Raymond Hettinger'ın 2018 PyCon sunumu Python Dataclasses'a başlamak için mükemmel bir yoldur.
Sarath Chandra

1

Nesnelerin örneklerini karşılaştırırken __cmp__işlev çağrılır.

== işleci varsayılan olarak sizin için __cmp__çalışmıyorsa, nesnenin işlevini her zaman yeniden tanımlayabilirsiniz .

Düzenle:

Belirtildiği gibi, __cmp__işlev 3.0'dan beri kullanımdan kaldırılmıştır. Bunun yerine “zengin karşılaştırma” yöntemlerini kullanmalısınız.


1
Cmp fonksiyonu 3.0+ için kullanımdan kaldırıldı
Christopher

1

Bir veya daha fazla sınıfları ile konum uğraşan Eğer varsa edemez içeriden değiştirmek de bir fark özgü kütüphanesine bağlı olmadığını Bunu yapmak için genel ve basit yolu vardır:

En kolay, çok karmaşık nesneler için güvenli olmayan yöntem

pickle.dumps(a) == pickle.dumps(b)

picklePython nesneleri için çok yaygın bir serileştirme kütüphanesidir ve böylece hemen hemen her şeyi serileştirebilecektir. Yukarıdaki kod parçasında strserileştirilmiş aolan ile karşılaştırıyorum b. Bir sonraki yöntemin aksine, bu da özel sınıfları kontrol etme avantajına sahiptir.

En büyük güçlük: belirli sıralama ve [de / en] kodlama yöntemleri picklenedeniyle , özellikle sık karşılaşacağınız gibi daha karmaşık olanlarla (örneğin iç içe geçmiş özel sınıf örneklerinin listeleri) uğraşırken eşit nesneler için aynı sonucu vermeyebilir. bazı üçüncü taraf kütüphanelerinde. Bu durumlar için farklı bir yaklaşım öneriyorum:

Kapsamlı, her nesne için güvenli yöntem

Size serileştirilebilir nesneler verecek yinelemeli bir yansıma yazabilir ve ardından sonuçları karşılaştırabilirsiniz

from collections.abc import Iterable

BASE_TYPES = [str, int, float, bool, type(None)]


def base_typed(obj):
    """Recursive reflection method to convert any object property into a comparable form.
    """
    T = type(obj)
    from_numpy = T.__module__ == 'numpy'

    if T in BASE_TYPES or callable(obj) or (from_numpy and not isinstance(T, Iterable)):
        return obj

    if isinstance(obj, Iterable):
        base_items = [base_typed(item) for item in obj]
        return base_items if from_numpy else T(base_items)

    d = obj if T is dict else obj.__dict__

    return {k: base_typed(v) for k, v in d.items()}


def deep_equals(*args):
    return all(base_typed(args[0]) == base_typed(other) for other in args[1:])

Şimdi nesnelerinizin ne olduğu önemli değil, derin eşitliğin çalışması güvence altında

>>> from sklearn.ensemble import RandomForestClassifier
>>>
>>> a = RandomForestClassifier(max_depth=2, random_state=42)
>>> b = RandomForestClassifier(max_depth=2, random_state=42)
>>> 
>>> deep_equals(a, b)
True

Karşılaştırılabilirlerin sayısı da önemli değil

>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False

Bunun için benim durumum BDD testleri içinde zaten eğitilmiş olan çeşitli Machine Learning modelleri arasında derin eşitliği kontrol etmekti . Modeller çok çeşitli üçüncü taraf kütüphanelerine aitti. Kesinlikle __eq__burada diğer cevaplar gibi uygulamak benim için bir seçenek değildi.

Tüm üsleri kapsayan

Karşılaştırılan bir veya daha fazla özel sınıfın bir __dict__uygulaması olmadığı bir senaryoda olabilirsiniz . Bu, hiçbir şekilde yaygın değil ama sklearn en Rastgele Orman sınıflandırıcı içerisinde bir alt türü geçerlidir: <type 'sklearn.tree._tree.Tree'>. Bu durumları duruma göre ele alın - örneğin, özellikle , etkilenen türün içeriğini, örnek hakkında temsili bilgi veren bir yöntemin içeriğiyle değiştirmeye karar verdim (bu durumda __getstate__yöntem). Böyle, ikinci-to-son sırada base_typedoldu

d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()

Düzenleme: organizasyonun uğruna, ben son iki satır değiştirilir base_typedile return dict_from(obj)(Ben, size Doc2Vec arıyorum) ve daha belirsiz kütüphanelerini barındıracak şekilde gerçekten jenerik yansıması hayata

def isproperty(prop, obj):
    return not callable(getattr(obj, prop)) and not prop.startswith('_')


def dict_from(obj):
    """Converts dict-like objects into dicts
    """
    if isinstance(obj, dict):
        # Dict and subtypes are directly converted
        d = dict(obj)

    elif '__dict__' in dir(obj):
        d = obj.__dict__

    elif str(type(obj)) == 'sklearn.tree._tree.Tree':
        # Replaces sklearn trees with their state metadata
        d = obj.__getstate__()

    else:
        # Extract non-callable, non-private attributes with reflection
        kv = [(p, getattr(obj, p)) for p in dir(obj) if isproperty(p, obj)]
        d = {k: v for k, v in kv}

    return {k: base_typed(v) for k, v in d.items()}

Yukarıdaki yöntemlerden hiçbirinin True, aynı anahtar / değer çiftlerine sahip ancak farklı anahtar / değer siparişlerine sahip farklı nesneler için vermediğini unutmayın .

>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False

Ancak isterseniz Python'un yerleşik sortedyöntemini önceden kullanabilirsiniz.


0

Bunu yazdım ve projemdeki bir test/utilsmodüle yerleştirdim . Bir sınıf olmadığı durumlarda, sadece planlayın, bu her iki nesneyi de geçecek ve

  1. her özellik karşılıklarına eşittir
  2. Sarkan özellik yok (yalnızca bir nesnede var olan attrler)

Onun büyük ... seksi değil ... ama oh boi işe yarıyor!

def assertObjectsEqual(obj_a, obj_b):

    def _assert(a, b):
        if a == b:
            return
        raise AssertionError(f'{a} !== {b} inside assertObjectsEqual')

    def _check(a, b):
        if a is None or b is None:
            _assert(a, b)
        for k,v in a.items():
            if isinstance(v, dict):
                assertObjectsEqual(v, b[k])
            else:
                _assert(v, b[k])

    # Asserting both directions is more work
    # but it ensures no dangling values on
    # on either object
    _check(obj_a, obj_b)
    _check(obj_b, obj_a)

Bunu kaldırarak _assertve sadece düz ol 'kullanarak biraz temizleyebilirsiniz, assertancak başarısız olduğunda aldığınız mesaj çok yararsızdır.


0

Yöntemi uygulamalısınız __eq__:

 class MyClass:
      def __init__(self, foo, bar, name):
           self.foo = foo
           self.bar = bar
           self.name = name

      def __eq__(self,other):
           if not isinstance(other,MyClass):
                return NotImplemented
           else:
                #string lists of all method names and properties of each of these objects
                prop_names1 = list(self.__dict__)
                prop_names2 = list(other.__dict__)

                n = len(prop_names1) #number of properties
                for i in range(n):
                     if getattr(self,prop_names1[i]) != getattr(other,prop_names2[i]):
                          return False

                return True

2
Lütfen cevabınızı düzenleyin ve kodunuza diğer on cevabın neden farklı olduğunu açıklayan daha fazla açıklama ekleyin. Bu soru on yaşında ve zaten kabul edilmiş bir cevabı ve birkaç çok kaliteli cevabı var. Ek ayrıntılar olmadan, cevabınız diğerlerine göre çok daha düşük kalitededir ve büyük olasılıkla indirgenmeyecek veya silinecektir.
Das_Geek

0

Aşağıda iki nesne hiyerarşisi arasında derin karşılaştırma yaparak çalışır (sınırlı testlerimde). Nesnelerin kendilerinin veya özniteliklerinin sözlük olduğu durumlar da dahil olmak üzere çeşitli durumlarda.

def deep_comp(o1:Any, o2:Any)->bool:
    # NOTE: dict don't have __dict__
    o1d = getattr(o1, '__dict__', None)
    o2d = getattr(o2, '__dict__', None)

    # if both are objects
    if o1d is not None and o2d is not None:
        # we will compare their dictionaries
        o1, o2 = o1.__dict__, o2.__dict__

    if o1 is not None and o2 is not None:
        # if both are dictionaries, we will compare each key
        if isinstance(o1, dict) and isinstance(o2, dict):
            for k in set().union(o1.keys() ,o2.keys()):
                if k in o1 and k in o2:
                    if not deep_comp(o1[k], o2[k]):
                        return False
                else:
                    return False # some key missing
            return True
    # mismatched object types or both are scalers, or one or both None
    return o1 == o2

Bu çok zor bir kod, bu yüzden lütfen yorumlarda sizin için işe yaramayabilecek durumları ekleyin.


0
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

    def __repr__(self):
        return str(self.value)

    def __eq__(self,other):
        return self.value == other.value

node1 = Node(1)
node2 = Node(1)

print(f'node1 id:{id(node1)}')
print(f'node2 id:{id(node2)}')
print(node1 == node2)
>>> node1 id:4396696848
>>> node2 id:4396698000
>>> True

-1

Bir özniteliğe göre öznitelik karşılaştırması almak ve bunun başarısız olup olmadığını ve nerede başarısız olduğunu görmek istiyorsanız, aşağıdaki liste anlayışını kullanabilirsiniz:

[i for i,j in 
 zip([getattr(obj_1, attr) for attr in dir(obj_1)],
     [getattr(obj_2, attr) for attr in dir(obj_2)]) 
 if not i==j]

Buradaki ek avantaj, bir satır sıkıştırıp PyCharm'da hata ayıklarken "İfadeyi Değerlendir" penceresine girebilmenizdir.


-3

İlk örneği denedim (yukarıdaki 7'ye bakın) ve ipython'da çalışmadı. İki özdeş nesne örneği kullanılarak uygulandığında cmp (obj1, obj2) işlevinin "1" döndürdüğünü unutmayın. Özellik değerlerinden birini değiştirip yeniden karşılaştırdığımda tuhaf bir şekilde, cmp (obj1, obj2) kullanarak nesne bir "1" döndürmeye devam ediyor. (iç çekmek...)

Tamam, yapmanız gereken iki nesneyi yinelemek ve == işaretini kullanarak her özniteliği karşılaştırmak.


En azından Python 2.7'de, nesneler varsayılan olarak kimlikle karşılaştırılır. Bu, CPython için pratikte bellek adresleriyle karşılaştırdıkları anlamına gelir. Bu nedenle cmp (o1, o2) yalnızca "o1 o2" olduğunda 0 döndürür ve id (o1) ve id (o2) değerlerine bağlı olarak sürekli olarak 1 veya -1 olur
yacc143

-6

== ile karşılaştırıldığında bir sınıf örneği eşit değildir. En iyi yolu, cmp işlevini sınıfınıza yapacak olan şeyleri yapmaktır.

İçeriğe göre karşılaştırma yapmak istiyorsanız sadece cmp (obj1, obj2) kullanabilirsiniz.

Vaka cmp'nizde (doc1, doc2) İçerikler aynıysa akıllıca -1 döndürür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.