问题:比较对象实例的属性是否相等
我有一个类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')
但是,当我比较它们的相等性时,Python返回False
:
>>> x == y
False
如何使python认为这两个对象相等?
I have a class MyClass
, which contains two member variables foo
and bar
:
class MyClass:
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
I have two instances of this class, each of which has identical values for foo
and bar
:
x = MyClass('foo', 'bar')
y = MyClass('foo', 'bar')
However, when I compare them for equality, Python returns False
:
>>> x == y
False
How can I make python consider these two objects equal?
回答 0
您应该实现以下方法__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
可能在对象的生存期内更改),则建议仅使实例保持不可散列状态。
如果要对不可变类型进行建模,则还应该实现datamodel hook __hash__
:
class MyClass:
...
def __hash__(self):
# necessary for instances to behave sanely in dicts and sets.
return hash((self.foo, self.bar))
__dict__
不建议使用通用的解决方案,例如遍历和比较值的想法-它永远不可能真正通用,因为其中__dict__
可能包含不可比较或不可哈希的类型。
注意:请注意,在Python 3之前,您可能需要使用__cmp__
而不是__eq__
。Python 2用户可能还想实现__ne__
,因为不等式的明智的默认行为(即反转相等结果)不会在Python 2中自动创建。
You should implement the method __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
Now it outputs:
>>> x == y
True
Note that implementing __eq__
will automatically make instances of your class unhashable, which means they can’t be stored in sets and dicts. If you’re not modelling an immutable type (i.e. if the attributes foo
and bar
may change value within the lifetime of your object), then it’s recommend to just leave your instances as unhashable.
If you are modelling an immutable type, you should also implement the datamodel hook __hash__
:
class MyClass:
...
def __hash__(self):
# necessary for instances to behave sanely in dicts and sets.
return hash((self.foo, self.bar))
A general solution, like the idea of looping through __dict__
and comparing values, is not advisable – it can never be truly general because the __dict__
may have uncomparable or unhashable types contained within.
N.B.: be aware that before Python 3, you may need to use __cmp__
instead of __eq__
. Python 2 users may also want to implement __ne__
, since a sensible default behaviour for inequality (i.e. inverting the equality result) will not be automatically created in Python 2.
回答 1
您将覆盖对象中的丰富比较运算符。
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
You override the rich comparison operators in your object.
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
Like this:
def __eq__(self, other):
return self._id == other._id
回答 2
__eq__
在您的类中实现该方法;像这样的东西:
def __eq__(self, other):
return self.path == other.path and self.title == other.title
编辑:如果您希望您的对象比较且仅当它们具有相等的实例字典时才比较:
def __eq__(self, other):
return self.__dict__ == other.__dict__
Implement the __eq__
method in your class; something like this:
def __eq__(self, other):
return self.path == other.path and self.title == other.title
Edit: if you want your objects to compare equal if and only if they have equal instance dictionaries:
def __eq__(self, other):
return self.__dict__ == other.__dict__
回答 3
总结一下:
- 建议您执行
__eq__
而不是__cmp__
,除非您运行python <= 2.0(__eq__
已在2.1中添加)
- 别忘了也要实现
__ne__
(应该是类似的东西return not self.__eq__(other)
或return not self == other
非常特殊的情况除外)
- 不要忘记,必须在要比较的每个自定义类中实现运算符(请参见下面的示例)。
如果要与可以为None的对象进行比较,则必须实现它。解释器无法猜测…(请参见下面的示例)
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)
As a summary :
- It’s advised to implement
__eq__
rather than __cmp__
, except if you run python <= 2.0 (__eq__
has been added in 2.1)
- Don’t forget to also implement
__ne__
(should be something like return not self.__eq__(other)
or return not self == other
except very special case)
- Don`t forget that the operator must be implemented in each custom class you want to compare (see example below).
If you want to compare with object that can be None, you must implement it. The interpreter cannot guess it … (see example below)
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)
回答 4
回答 5
使用Python 3.7(及更高版本)中的数据类,比较对象实例是否相等是一项内置功能。
数据类的反向移植适用于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
With Dataclasses in Python 3.7 (and above), a comparison of object instances for equality is an inbuilt feature.
A backport for Dataclasses is available for 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
回答 6
比较对象实例时,将__cmp__
调用该函数。
如果默认情况下==运算符对您不起作用,则始终可以__cmp__
为该对象重新定义函数。
编辑:
正如已经指出的那样,__cmp__
自3.0起不推荐使用该功能。相反,您应该使用“丰富比较”方法。
When comparing instances of objects, the __cmp__
function is called.
If the == operator is not working for you by default, you can always redefine the __cmp__
function for the object.
Edit:
As has been pointed out, the __cmp__
function is deprecated since 3.0.
Instead you should use the “rich comparison” methods.
回答 7
我编写了此代码并将其放在test/utils
我项目的模块中。对于不是Class的情况,只需按计划进行,这将遍历两个对象并确保
- 每个属性都等于对应属性
- 不存在悬挂属性(仅在一个对象上存在的属性)
大…不性感 …但是噢,它起作用了!
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
并仅使用普通ol’ 对其进行清理,assert
但失败时收到的消息将无济于事。
I wrote this and placed it in a test/utils
module in my project. For cases when its not a class, just plan ol’ dict, this will traverse both objects and ensure
- every attribute is equal to its counterpart
- No dangling attributes exist (attrs that only exist on one object)
Its big… its not sexy… but oh boi does it work!
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)
You can clean it up a little by removing the _assert
and just using plain ol’ assert
but then the message you get when it fails is very unhelpful.
回答 8
您应该实现以下方法__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
You should implement the method __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
回答 9
下面的工作(在我有限的测试中)是通过对两个对象层次结构进行深入比较而实现的。在处理各种情况下,包括对象本身或其属性为字典的情况。
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
这是一个非常棘手的代码,因此请在注释中添加任何可能不适合您的情况。
Below works (in my limited testing) by doing deep compare between two object hierarchies. In handles various cases including the cases when objects themselves or their attributes are dictionaries.
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
This is a very tricky code so please add any cases that might not work for you in comments.
回答 10
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
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
回答 11
如果你正在处理一个或多个类,你不能从内部更改的,则有一些通用且简单的方法可以执行此操作,而这些方法也不依赖于diff特定的库:
最简单,非常不安全的复杂对象方法
pickle.dumps(a) == pickle.dumps(b)
pickle
是用于Python对象的非常常见的序列化库,因此实际上可以对几乎所有内容进行序列化。在上面的代码段中,我正在将str
from序列化为a
fromb
。与下一种方法不同,该方法还具有对自定义类进行类型检查的优点。
最大的麻烦:由于特定的排序和[de / en]编码方法,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__
实现的情况下。无论如何,这并不普遍,但是sklearn的Random Forest分类器中的一个子类型就是这种情况<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)
,并实现了真正通用的反映它容纳更多的晦涩库(我看着你,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
预先使用Python的内置方法。
If you’re dealing with one or more classes which you can’t change from the inside, there are generic and simple ways to do this that also don’t depend on a diff-specific library:
Easiest, unsafe-for-very-complex-objects method
pickle.dumps(a) == pickle.dumps(b)
pickle
is a very common serialization lib for Python objects, and will thus be able to serialize pretty much anything, really. In the above snippet I’m comparing the str
from serialized a
with the one from b
. Unlike the next method, this one has the advantage of also type checking custom classes.
The biggest hassle: due to specific ordering and [de/en]coding methods, pickle
may not yield the same result for equal objects, specially when dealing with more complex ones (e.g. lists of nested custom-class instances) like you’ll frequently find in some third-party libs. For those cases, I’d recommend a different approach:
Thorough, safe-for-any-object method
You could write a recursive reflection that’ll give you serializable objects, and then compare results
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:])
Now it doesn’t matter what your objects are, deep equality is assured to work
>>> 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
The number of comparables doesn’t matter as well
>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False
My use case for this was checking deep equality among a diverse set of already trained Machine Learning models inside BDD tests. The models belonged to a diverse set of third-party libs. Certainly implementing __eq__
like other answers here suggest wasn’t an option for me.
Covering all the bases
You may be in a scenario where one or more of the custom classes being compared do not have a __dict__
implementation. That’s not common by any means, but it is the case of a subtype within sklearn’s Random Forest classifier: <type 'sklearn.tree._tree.Tree'>
. Treat these situations in a case by case basis – e.g. specifically, I decided to replace the content of the afflicted type with the content of a method that gives me representative information on the instance (in this case, the __getstate__
method). For such, the second-to-last row in base_typed
became
d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()
Edit: for the sake of organization, I replaced the hideous oneliner above with return dict_from(obj)
. Here, dict_from
is a really generic reflection made to accommodate more obscure libs (I’m looking at you, 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):
# Use standard dict representation when available
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()}
Do mind none of the above methods yield True
for objects with the same key-value pairs in differing order, as in
>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False
But if you want that you could use Python’s built-in sorted
method beforehand anyway.
回答 12
如果要获得逐个属性的比较,并查看它是否失败以及在何处失败,则可以使用以下列表理解:
[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]
在此的额外好处是,您可以将其压缩一行,然后在PyCharm中进行调试时输入“评估表达式”窗口。
If you want to get an attribute-by-attribute comparison, and see if and where it fails, you can use the following list comprehension:
[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]
The extra advantage here is that you can squeeze it one line and enter in the “Evaluate Expression” window when debugging in PyCharm.
回答 13
我尝试了初始示例(请参见上面的7),但在ipython中不起作用。请注意,使用两个相同的对象实例实现时,cmp(obj1,obj2)返回“ 1”。奇怪的是,当我修改一个属性值并重新比较时,使用cmp(obj1,obj2),该对象将继续返回“ 1”。(叹…)
好的,所以您需要做的是迭代两个对象,并使用==符号比较每个属性。
I tried the initial example (see 7 above) and it did not work in ipython. Note that cmp(obj1,obj2) returns a “1” when implemented using two identical object instances. Oddly enough when I modify one of the attribute values and recompare, using cmp(obj1,obj2) the object continues to return a “1”. (sigh…)
Ok, so what you need to do is iterate two objects and compare each attribute using the == sign.
回答 14
与==比较时,类的实例不相等。最好的方法是屁股CMP函数添加到您的类中,这将完成任务。
如果要按内容进行比较,可以简单地使用cmp(obj1,obj2)
在您的情况下,如果cmp(doc1,doc2)的内容相同,则它将返回-1。
Instance of a class when compared with == comes to non-equal. The best way is to ass the cmp function to your class which will do the stuff.
If you want to do comparison by the content you can simply use cmp(obj1,obj2)
In your case cmp(doc1,doc2) It will return -1 if the content wise they are same.