उनकी विशेषताओं द्वारा समानता के लिए वस्तु उदाहरणों की तुलना करें


244

मेरे पास एक वर्ग है MyClass, जिसमें दो सदस्य चर हैं fooऔर bar:

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

मेरे पास इस वर्ग के दो उदाहरण हैं, जिनमें से प्रत्येक के लिए समान मूल्य हैं fooऔर bar:

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

हालाँकि, जब मैं उनकी तुलना समानता के लिए करता हूँ, तो पायथन लौटता है False:

>>> x == y
False

मैं अजगर को इन दो वस्तुओं के बराबर कैसे बना सकता हूं?

जवाबों:


354

आपको विधि लागू करनी चाहिए __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

अब यह आउटपुट:

>>> x == y
True

ध्यान दें कि लागू करने __eq__से स्वचालित रूप से आपकी कक्षा के इंस्टेंसेस सहज हो जाएंगे, जिसका अर्थ है कि उन्हें सेट और डाइक में संग्रहीत नहीं किया जा सकता है। यदि आप एक अपरिवर्तनीय प्रकार की मॉडलिंग नहीं कर रहे हैं (अर्थात यदि गुण fooऔर barआपकी वस्तु के जीवनकाल के भीतर मूल्य बदल सकते हैं), तो यह सिर्फ आपके उदाहरणों को अप्राप्य के रूप में छोड़ने की सिफारिश करता है।

यदि आप एक अपरिवर्तनीय प्रकार की मॉडलिंग कर रहे हैं, तो आपको डेटामॉडल हुक भी लागू करना चाहिए __hash__:

class MyClass:
    ...

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

__dict__मूल्यों के माध्यम से और तुलना करने के विचार की तरह एक सामान्य समाधान, उचित नहीं है - यह वास्तव में कभी भी सामान्य नहीं हो सकता क्योंकि __dict__हो सकता है कि इसमें अतुलनीय या उपलब्ध प्रकार न हों।

एनबी: ध्यान रखें कि पायथन 3 से पहले, आपको __cmp__इसके बजाय उपयोग करने की आवश्यकता हो सकती है __eq__। अजगर 2 उपयोगकर्ताओं को भी लागू करना चाह सकते हैं __ne__, क्योंकि असमानता के लिए एक समझदार डिफ़ॉल्ट व्यवहार (यानी समानता परिणाम प्राप्त करना) स्वचालित रूप से पायथन 2 में नहीं बनाया जाएगा।


2
मैं return NotImplemented(उठाने के बजाय NotImplementedError) के उपयोग को लेकर उत्सुक था । उस विषय को यहां शामिल किया गया है: stackoverflow.com/questions/878943/…
init_js

48

आप अपने ऑब्जेक्ट में समृद्ध तुलना ऑपरेटरों को ओवरराइड करते हैं

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

ऐशे ही:

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

3
ध्यान दें कि अजगर 2.5 में और उसके बाद, वर्ग को परिभाषित करना होगा __eq__(), लेकिन का केवल एक __lt__(), __le__(), __gt__(), या __ge__()कि के अलावा जरूरत है। उस से, पायथन अन्य तरीकों का अनुमान लगा सकता है। functoolsअधिक जानकारी के लिए देखें ।
kba

1
@ अब्बा, मुझे नहीं लगता कि यह सच है। यह functoolsमॉड्यूल के लिए काम कर सकता है , लेकिन मानक तुलनित्र के लिए काम नहीं करता है : MyObj1 != Myobj2केवल तभी काम करेगा जब __ne__()विधि लागू की जाती है।
अरेल

6
फंक्शंस के बारे में विशिष्ट टिप @functools.total_orderingअपने वर्ग पर डेकोरेटर का उपयोग करने के लिए होनी चाहिए , फिर ऊपर के रूप में आप सिर्फ __eq__एक और एक को परिभाषित कर सकते हैं और बाकी व्युत्पन्न होंगे
एंथ्रोपिक

7

__eq__अपनी कक्षा में विधि को लागू करें ; कुछ इस तरह:

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

संपादित करें: यदि आप चाहते हैं कि आपकी वस्तुओं की तुलना समान और केवल तभी हो जब उनके पास समान उदाहरण हो:

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

शायद आपको यह self is otherदेखने का मतलब है कि क्या वे एक ही वस्तु हैं।
S.Lott

2
-1। यहां तक ​​कि अगर यह दो शब्दकोश उदाहरण है, तो पायथन उनकी तुलना कुंजी / मूल्यों से स्वचालित रूप से करेगा। यह जावा नहीं है ...
e-satis

पहला समाधान एक उठा सकता है AttributeError। आपको पंक्ति सम्मिलित करनी होगी if hasattr(other, "path") and hasattr(other, "title"):(जैसे पायथन प्रलेखन में यह अच्छा उदाहरण )।
मैगीयरो

5

सारांश के रूप में:

  1. इसके __eq__बजाय इसे लागू करने की सलाह दी जाती है __cmp__, सिवाय इसके कि क्या आप अजगर <= 2.0 चलाते हैं ( __eq__2.1 में जोड़ा गया है)
  2. लागू करने के लिए मत भूलना __ne__( बहुत विशेष मामले को छोड़कर return not self.__eq__(other)या जैसे कुछ होना चाहिए return not self == other)
  3. डॉन `टी भूल जाते हैं कि ऑपरेटर को प्रत्येक कस्टम वर्ग में लागू किया जाना चाहिए जिसे आप तुलना करना चाहते हैं (नीचे उदाहरण देखें)।
  4. यदि आप किसी ऐसी वस्तु से तुलना करना चाहते हैं जो कोई नहीं हो सकती है, तो आपको इसे लागू करना होगा। दुभाषिया इसका अनुमान नहीं लगा सकता है ... (नीचे उदाहरण देखें)

    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

आपके विशिष्ट मामले के आधार पर, आप कर सकते हैं:

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

एक वस्तु के खेतों से अजगर शब्दकोश देखें


इसके अलावा दिलचस्प है, जबकि vars एक तानाशाही देता है, unittest का मुखर काम नहीं करता है, भले ही दृश्य समीक्षा से पता चलता है कि वे वास्तव में, समान हैं। मैंने इसे चारों ओर घुमाकर डिंग्स को स्ट्रिंग्स में बदल दिया और उनकी तुलना की: self.assertEqual (str (vars (tbl0)), str (var_tbl0))
Ben

2

पायथन 3.7 (और उससे अधिक) में डाटाक्लास के साथ , समानता के लिए वस्तु उदाहरणों की तुलना एक इनबिल्ट सुविधा है।

डेटाकेल्स के लिए एक बैकपोर्ट पायथन 3.6 के लिए उपलब्ध है।

(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

रेमंड हेटिंगर की 2018 पाइकॉन प्रस्तुति , पायथन डेटाकेल्स के साथ आरंभ करने का एक शानदार तरीका है।
सारथ चंद्र

1

वस्तुओं के उदाहरणों की तुलना करते समय, __cmp__फ़ंक्शन को कहा जाता है।

यदि == ऑपरेटर डिफ़ॉल्ट रूप से आपके लिए काम नहीं कर रहा है, तो आप हमेशा __cmp__ऑब्जेक्ट के लिए फ़ंक्शन को फिर से परिभाषित कर सकते हैं ।

संपादित करें:

जैसा कि बताया गया है, __cmp__फ़ंक्शन 3.0 से हटा दिया गया है। इसके बजाय आपको "समृद्ध तुलना" विधियों का उपयोग करना चाहिए ।


1
सीएमपी समारोह 3.0+ के लिए हटा दिया गया है
क्रिस्टोफर

1

यदि आप एक या अधिक वर्गों के साथ काम कर रहे हैं, जो आप अंदर से नहीं बदल सकते हैं , तो ऐसा करने के लिए सामान्य और सरल तरीके हैं जो एक विशिष्ट-विशिष्ट पुस्तकालय पर निर्भर नहीं करते हैं:

सबसे आसान, असुरक्षित-बहुत-जटिल-वस्तुओं के लिए विधि

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

pickleपायथन वस्तुओं के लिए एक बहुत ही सामान्य क्रमबद्धता परिवाद है, और इस प्रकार वास्तव में बहुत अधिक कुछ भी प्रसारित करने में सक्षम होगा। उपरोक्त स्निपेट में मैं strधारावाहिक से तुलना कर रहा हूं aजिसमें से एक है b। अगली विधि के विपरीत, यह भी कस्टम वर्गों की जाँच प्रकार का लाभ है।

सबसे बड़ी परेशानी: विशिष्ट आदेश देने और [डी / एन] कोडिंग विधियों के pickleकारण, समान वस्तुओं के लिए एक ही परिणाम नहीं मिल सकता है , खासकर जब अधिक जटिल लोगों (जैसे नेस्टेड कस्टम-क्लास इंस्टेंसेस की सूची) जैसे कि आप अक्सर पाएंगे। कुछ तृतीय-पक्षीय परिवादों में। उन मामलों के लिए, मैं एक अलग दृष्टिकोण सुझाऊँगा:

पूरी तरह से, सुरक्षित-किसी भी वस्तु के लिए विधि

आप एक पुनरावर्ती प्रतिबिंब लिख सकते हैं जो आपको क्रमबद्ध वस्तुएं देगा, और फिर परिणामों की तुलना करेगा

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:])

अब इससे कोई फर्क नहीं पड़ता कि आपकी वस्तुएं क्या हैं, गहरी समानता काम करने का आश्वासन देती है

>>> 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

तुलनाओं की संख्या भी मायने नहीं रखती है

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

इसके लिए मेरा उपयोग मामला BDD परीक्षणों के अंदर पहले से प्रशिक्षित मशीन लर्निंग मॉडल के विविध सेट के बीच गहरी समानता की जाँच कर रहा था । मॉडल तीसरे पक्ष के लिबास के विविध सेट के थे। निश्चित रूप से __eq__अन्य उत्तरों की तरह यहां लागू करना मेरे लिए एक विकल्प नहीं था।

सभी ठिकानों को कवर करना

आप एक ऐसे परिदृश्य में हो सकते हैं जहां एक या एक से अधिक कस्टम वर्ग की तुलना में __dict__कार्यान्वयन नहीं होता है । यह किसी भी तरह से सामान्य नहीं है, लेकिन यह स्केलेर के रैंडम फॉरेस्ट क्लासिफायर के भीतर एक उपप्रकार का मामला है <type 'sklearn.tree._tree.Tree'>:। केस के आधार पर किसी स्थिति में इन स्थितियों का इलाज करें - उदाहरण के लिए , विशेष रूप से , मैंने पीड़ित प्रकार की सामग्री को एक विधि की सामग्री के साथ बदलने का फैसला किया है जो मुझे उदाहरण पर प्रतिनिधि जानकारी देता है (इस मामले में, __getstate__विधि)। इस तरह, दूसरी-से-अंतिम पंक्ति base_typedबन गई

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

संपादित करें: संगठन के लिए, मैं के अंतिम दो पंक्तियों की जगह base_typedके साथ return dict_from(obj), और अधिक अस्पष्ट libs समायोजित करने के लिए एक बहुत सामान्य प्रतिबिंब लागू किया (मैं, तुम पर देख रहा हूँ Doc2Vec)

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()}

उपरोक्त विधियों में से कोई भी Trueएक ही कुंजी-मान वाले जोड़े के साथ अलग-अलग ऑब्जेक्ट्स के लिए उपज नहीं है, लेकिन अलग-अलग कुंजी / मूल्य आदेश, जैसे

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

लेकिन अगर आप चाहते हैं कि आप sortedवैसे भी पहले से पाइथन की अंतर्निहित विधि का उपयोग कर सकते हैं ।


0

मैंने इसे लिखा और test/utilsअपनी परियोजना में एक मॉड्यूल में रखा । ऐसे मामलों के लिए जब इसका वर्ग नहीं है, बस ऑल्ट की योजना बनाएं, यह दोनों वस्तुओं को पीछे छोड़ देगा और सुनिश्चित करेगा

  1. हर विशेषता अपने समकक्ष के बराबर है
  2. कोई झूलने की विशेषताएँ मौजूद नहीं हैं (केवल एक वस्तु पर मौजूद है)

इसकी बड़ी ... इसकी सेक्सी नहीं ... लेकिन ओह बोई यह काम करता है!

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)

आप इसे हटाकर इसे साफ कर सकते हैं _assertऔर सिर्फ सादे ओल का उपयोग कर सकते हैं ' assertलेकिन तब आपको जो संदेश मिलता है वह विफल होता है।


0

आपको विधि लागू करनी चाहिए __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
कृपया अपना उत्तर संपादित करें और अपने कोड में और स्पष्टीकरण जोड़ते हुए बताएं कि यह दस अन्य उत्तरों से अलग क्यों है। यह सवाल दस साल पुराना है , और पहले से ही एक स्वीकृत उत्तर और कई बहुत उच्च गुणवत्ता वाले हैं। अतिरिक्त विवरण के बिना, आपका जवाब दूसरों की तुलना में बहुत कम गुणवत्ता का है, और सबसे अधिक संभावना कम या हटा दी जाएगी।
दास_गीक

0

नीचे दो ऑब्जेक्ट पदानुक्रम के बीच गहरी तुलना करके (मेरे सीमित परीक्षण में) काम करता है। उन मामलों सहित विभिन्न मामलों को संभालता है जब ऑब्जेक्ट स्वयं या उनकी विशेषताओं के शब्दकोष होते हैं।

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

यह एक बहुत ही मुश्किल कोड है, इसलिए कृपया कोई भी मामला जोड़ें जो आपके लिए टिप्पणियों में काम न करें।


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

यदि आप एक विशेषता-दर-विशेषता तुलना प्राप्त करना चाहते हैं, और देखें कि क्या और कहाँ यह विफल रहता है, तो आप निम्न सूची समझ का उपयोग कर सकते हैं:

[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]

यहां अतिरिक्त लाभ यह है कि आप इसे एक पंक्ति में निचोड़ सकते हैं और पाइक्रोम में डीबगिंग करते समय "मूल्यांकन का मूल्यांकन करें" विंडो में प्रवेश कर सकते हैं।


-3

मैंने शुरुआती उदाहरण की कोशिश की (ऊपर 7 देखें) और यह ipython में काम नहीं किया। ध्यान दें कि cmp (obj1, obj2) दो समान वस्तु उदाहरणों का उपयोग करते हुए "1" लौटाता है। अजीब तरह से पर्याप्त है जब मैं एक विशेषता मान और पुनर्संयोजन को संशोधित करता हूं, तो cmp (obj1, obj2) का उपयोग करके ऑब्जेक्ट "1" वापस लौटाता रहता है। (आह ...)

ठीक है, तो आपको क्या करने की आवश्यकता है दो वस्तुओं को पुनरावृत्त करें और == चिह्न का उपयोग करके प्रत्येक विशेषता की तुलना करें।


पायथन 2.7 में कम से कम, वस्तुओं की पहचान डिफ़ॉल्ट रूप से की जाती है। इसका मतलब है कि CPython के लिए व्यावहारिक शब्दों में वे स्मृति पते द्वारा तुलना करते हैं। इसीलिए cmp (o1, o2) 0 तभी प्राप्त होता है जब "o1 o2" होता है और id (o1) और id (o2) के मूल्यों के आधार पर लगातार 1 या -1
yacc143

-6

एक वर्ग का उदाहरण जब == के साथ तुलना की जाती है तो गैर-बराबर होता है। सबसे अच्छा तरीका है कि आप अपनी क्लास में cmp फंक्शन को एंजॉय करें जो सामान करेगा।

यदि आप उस सामग्री से तुलना करना चाहते हैं जिसे आप केवल cmp (obj1, obj2) का उपयोग कर सकते हैं

आपके मामले में cmp (doc1, doc2) यह -1 वापस आएगा यदि सामग्री के अनुसार वे समान हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.