So sánh các thể hiện đối tượng cho đẳng thức theo các thuộc tính của chúng


243

Tôi có một lớp MyClass, chứa hai biến thành viên foobar:

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

Tôi có hai trường hợp của lớp này, mỗi trường hợp có các giá trị giống nhau cho foobar:

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

Tuy nhiên, khi tôi so sánh chúng cho bằng nhau, Python trả về False:

>>> x == y
False

Làm thế nào tôi có thể làm cho python coi hai đối tượng bằng nhau?

Câu trả lời:


353

Bạn nên thực hiện phương pháp __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

Bây giờ nó xuất ra:

>>> x == y
True

Lưu ý rằng việc triển khai __eq__sẽ tự động làm cho các thể hiện của lớp của bạn không thể bị phá vỡ, điều đó có nghĩa là chúng không thể được lưu trữ theo bộ và ký tự. Nếu bạn không mô hình hóa một loại không thay đổi (nghĩa là nếu các thuộc tính foobarcó thể thay đổi giá trị trong vòng đời của đối tượng của bạn), thì bạn chỉ nên để các thể hiện của mình là không thể xóa được.

Nếu bạn đang lập mô hình một loại không thay đổi, bạn cũng nên thực hiện hook datamodel __hash__:

class MyClass:
    ...

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

Một giải pháp chung, như ý tưởng lặp qua __dict__và so sánh các giá trị, không được khuyến khích - nó không bao giờ có thể thực sự chung chung vì __dict__có thể có các loại không thể so sánh được hoặc không thể khắc phục được chứa trong đó.

NB: lưu ý rằng trước Python 3, bạn có thể cần phải sử dụng __cmp__thay vì __eq__. Người dùng Python 2 cũng có thể muốn thực hiện __ne__, vì một hành vi mặc định hợp lý cho sự bất bình đẳng (nghĩa là đảo ngược kết quả bình đẳng) sẽ không được tạo tự động trong Python 2.


2
Tôi tò mò về việc sử dụng return NotImplemented(thay vì nuôi NotImplementedError). Chủ đề đó được đề cập ở đây: stackoverflow.com/questions/878943/ từ
init_js

48

Bạn ghi đè các toán tử so sánh phong phú trong đối tượng của bạn.

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

Như thế này:

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

3
Lưu ý rằng trong Python 2.5 và trở đi, các lớp phải xác định __eq__(), nhưng chỉ có một số __lt__(), __le__(), __gt__(), hoặc __ge__()là cần thiết, thêm vào đó. Từ đó, Python có thể suy ra các phương thức khác. Xem functoolsđể biết thêm thông tin.
kba

1
@kba, tôi không nghĩ đó là sự thật. Điều này có thể làm việc cho functoolsmô-đun, nhưng không hoạt động cho các bộ so sánh tiêu chuẩn: MyObj1 != Myobj2sẽ chỉ hoạt động nếu __ne__()phương thức được thực hiện.
Arel

6
lời khuyên cụ thể về funcools nên sử dụng trình @functools.total_orderingtrang trí trong lớp của bạn, sau đó như trên bạn có thể định nghĩa __eq__và một cái khác và phần còn lại sẽ được dẫn xuất
Anentropic

7

Thực hiện __eq__phương pháp trong lớp học của bạn; đại loại như thế này:

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

Chỉnh sửa: nếu bạn muốn các đối tượng của mình so sánh bằng nhau khi và chỉ khi chúng có từ điển thể hiện bằng nhau:

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

Có lẽ bạn có nghĩa là self is otherđể xem nếu họ là cùng một đối tượng.
S.Lott

2
-1. Ngay cả khi đây là hai phiên bản từ điển, Python sẽ tự động so sánh chúng bằng các khóa / giá trị. Đây không phải là Java ...
e-satis

Các giải pháp đầu tiên có thể nâng cao một AttributeError. Bạn phải chèn dòng if hasattr(other, "path") and hasattr(other, "title"):(như ví dụ hay này trong tài liệu Python).
Maggyero

5

Như một bản tóm tắt :

  1. Bạn nên thực hiện __eq__thay vì __cmp__, ngoại trừ nếu bạn chạy python <= 2.0 ( __eq__đã được thêm vào 2.1)
  2. Đừng quên thực hiện __ne__(nên là một cái gì đó giống return not self.__eq__(other)hoặc return not self == otherngoại trừ trường hợp rất đặc biệt)
  3. Đừng quên rằng toán tử phải được triển khai trong mỗi lớp tùy chỉnh mà bạn muốn so sánh (xem ví dụ bên dưới).
  4. Nếu bạn muốn so sánh với đối tượng có thể là Không, bạn phải thực hiện nó. Trình thông dịch không thể đoán nó ... (xem ví dụ bên dưới)

    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

Tùy thuộc vào trường hợp cụ thể của bạn, bạn có thể làm:

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

Xem từ điển Python từ các trường của đối tượng


Cũng thú vị, trong khi các vars trả về một dict, assertDictEqual không đáng tin cậy dường như không hoạt động, mặc dù đánh giá trực quan cho thấy rằng trên thực tế, chúng là như nhau. Tôi đã khắc phục điều này bằng cách biến các chuỗi thành chuỗi và so sánh chúng: self.assertEqual (str (vars (tbl0)), str (vars (local_tbl0)))
Ben

2

Với Dataclass trong Python 3.7 (trở lên), so sánh các thể hiện đối tượng cho sự bình đẳng là một tính năng sẵn có.

Một backport cho Dataclass có sẵn cho Python 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

Bài thuyết trình PyCon năm 2018 của Raymond Hettinger là một cách tuyệt vời để bắt đầu với Python Dataclass.
Sarath Chandra

1

Khi so sánh các thể hiện của các đối tượng, __cmp__hàm được gọi.

Nếu toán tử == không hoạt động cho bạn theo mặc định, bạn luôn có thể xác định lại __cmp__hàm cho đối tượng.

Biên tập:

Như đã được chỉ ra, __cmp__chức năng không được dùng kể từ 3.0. Thay vào đó, bạn nên sử dụng các phương pháp so sánh phong phú của người Viking .


1
Hàm cmp không được dùng cho 3.0+
Christopher

0

Tôi đã viết điều này và đặt nó trong một test/utilsmô-đun trong dự án của tôi. Đối với các trường hợp khi nó không phải là một lớp, chỉ cần lập kế hoạch ol 'dict, điều này sẽ đi qua cả hai đối tượng và đảm bảo

  1. mọi thuộc tính đều bằng đối tác của nó
  2. Không tồn tại thuộc tính lơ lửng (attrs chỉ tồn tại trên một đối tượng)

Nó lớn ... nó không gợi cảm ... nhưng oh boi không hoạt động!

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)

Bạn có thể dọn dẹp nó một chút bằng cách xóa _assertvà chỉ sử dụng đơn giản ol ' assertnhưng sau đó tin nhắn bạn nhận được khi thất bại là rất không có ích.


0

Bạn nên thực hiện phương pháp __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
Vui lòng chỉnh sửa câu trả lời của bạn và thêm giải thích cho mã của bạn, giải thích lý do tại sao nó khác với mười câu trả lời khác. Câu hỏi này đã mười năm tuổi , và đã có một câu trả lời được chấp nhận và một số câu trả lời rất chất lượng. Nếu không có thêm chi tiết, câu trả lời của bạn có chất lượng thấp hơn nhiều so với những câu hỏi khác, và rất có thể sẽ bị hạ cấp hoặc bị xóa.
Das_Geek

0

Dưới đây hoạt động (trong thử nghiệm giới hạn của tôi) bằng cách so sánh sâu giữa hai hệ thống phân cấp đối tượng. Trong xử lý các trường hợp khác nhau bao gồm cả các trường hợp khi bản thân các đối tượng hoặc thuộc tính của chúng là từ điển.

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

Đây là một mã rất phức tạp, vì vậy vui lòng thêm bất kỳ trường hợp nào có thể không hoạt động cho bạn trong các bình luận.


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

0

Nếu bạn đang làm việc với một hoặc nhiều lớp mà bạn không thể thay đổi từ bên trong, có những cách đơn giản và chung chung để làm điều này cũng không phụ thuộc vào một thư viện khác biệt:

Phương pháp dễ nhất, không an toàn cho các đối tượng rất phức tạp

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

picklelà một lib tuần tự hóa rất phổ biến cho các đối tượng Python, và do đó sẽ có thể tuần tự hóa khá nhiều thứ, thực sự. Trong đoạn trích trên, tôi đang so sánh strtừ từ nối tiếp avới từb . Không giống như phương thức tiếp theo, phương thức này có ưu điểm là cũng kiểm tra các lớp tùy chỉnh.

Rắc rối lớn nhất: do cách đặt hàng cụ thể và phương pháp mã hóa [de / en], picklecó thể không mang lại kết quả tương tự cho các đối tượng bằng nhau , đặc biệt khi xử lý các đối tượng phức tạp hơn (ví dụ: danh sách các trường hợp lớp tùy chỉnh lồng nhau) như bạn thường thấy trong một số libs của bên thứ ba. Đối với những trường hợp đó, tôi muốn giới thiệu một cách tiếp cận khác:

Phương pháp triệt để, an toàn cho mọi đối tượng

Bạn có thể viết một phản xạ đệ quy sẽ cung cấp cho bạn các đối tượng tuần tự hóa, và sau đó so sánh kết quả

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

Bây giờ không quan trọng đối tượng của bạn là gì, sự bình đẳng sâu sắc được đảm bảo để làm việc

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

Số lượng so sánh không quan trọng là tốt

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

Trường hợp sử dụng của tôi cho việc này là kiểm tra sự bình đẳng sâu sắc giữa một loạt các mô hình Machine Learning đã được đào tạo trong các bài kiểm tra BDD. Các mô hình thuộc về một loạt các libs của bên thứ ba. Chắc chắn thực hiện __eq__như các câu trả lời khác ở đây cho thấy không phải là một lựa chọn cho tôi.

Bao gồm tất cả các cơ sở

Bạn có thể đang ở trong một kịch bản trong đó một hoặc nhiều lớp tùy chỉnh được so sánh không có __dict__triển khai . Điều đó không phổ biến bởi bất kỳ phương tiện nào, nhưng đó là trường hợp của một kiểu con trong phân loại Rừng ngẫu nhiên của sklearn : <type 'sklearn.tree._tree.Tree'>. Xử lý các tình huống này trong từng trường hợp cụ thể - ví dụ cụ thể , tôi quyết định thay thế nội dung của loại bị ảnh hưởng bằng nội dung của phương thức cung cấp cho tôi thông tin đại diện về trường hợp (trong trường hợp này là __getstate__phương pháp). Vì vậy, hàng thứ hai đến cuối cùng base_typedđã trở thành

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

Chỉnh sửa: vì lợi ích của tổ chức, tôi đã thay thế hai dòng cuối cùng base_typedbằng return dict_from(obj)và thực hiện một phản ánh thực sự chung chung cho nó để phù hợp với các lib tối nghĩa hơn (Tôi đang nhìn vào bạn, 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()}

Đừng bận tâm, không có phương thức nào ở trên mang lại Truecho các đối tượng khác nhau có cùng cặp khóa-giá trị nhưng thứ tự khóa / giá trị khác nhau, như trong

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

Nhưng nếu bạn muốn rằng bạn có thể sử dụng sortedphương thức tích hợp sẵn của Python trước.


-1

Nếu bạn muốn có được một so sánh thuộc tính theo thuộc tính, và xem nếu nó thất bại và ở đâu, bạn có thể sử dụng cách hiểu danh sách sau đây:

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

Lợi thế bổ sung ở đây là bạn có thể ép nó một dòng và nhập vào cửa sổ "Đánh giá biểu thức" khi gỡ lỗi trong PyCharm.


-3

Tôi đã thử ví dụ ban đầu (xem 7 ở trên) và nó không hoạt động trong ipython. Lưu ý rằng cmp (obj1, obj2) trả về "1" khi được triển khai bằng hai thể hiện đối tượng giống hệt nhau. Thật kỳ lạ khi tôi sửa đổi một trong các giá trị thuộc tính và biên dịch lại, sử dụng cmp (obj1, obj2), đối tượng tiếp tục trả về "1". (thở dài...)

Ok, vì vậy, những gì bạn cần làm là lặp lại hai đối tượng và so sánh từng thuộc tính bằng dấu ==.


Trong Python 2.7 ít nhất, các đối tượng được so sánh theo danh tính theo mặc định. Điều đó có nghĩa là đối với CPython trong các từ thực tế mà họ so sánh bằng địa chỉ bộ nhớ. Đó là lý do tại sao cmp (o1, o2) chỉ trả về 0 khi "o1 là o2" và nhất quán 1 hoặc -1 tùy thuộc vào các giá trị của id (o1) và id (o2)
yacc143

-6

Trường hợp của một lớp khi so sánh với == đến không bằng nhau. Cách tốt nhất là xác nhận hàm cmp cho lớp của bạn, nó sẽ thực hiện công cụ.

Nếu bạn muốn so sánh theo nội dung, bạn chỉ cần sử dụng cmp (obj1, obj2)

Trong trường hợp của bạn cmp (doc1, doc2) Nó sẽ trả về -1 nếu nội dung khôn ngoan giống nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.