我今天遇到了这个有趣的例子
class TestableEq(object):
def __init__(self):
self.eq_run = False
def __eq__(self, other):
self.eq_run = True
if isinstance(other, TestableEq):
other.eq_run = True
return self is other
>>> eq = TestableEq()
>>> eq.eq_run
False
>>> eq == eq
True
>>> eq.eq_run
True
>>> eq = TestableEq()
>>> eq is eq
True
>>> eq.eq_run
False
>>> [eq] == [eq]
True
>>> eq.eq_run # Should be True, right?
False
>>> (eq,) == (eq,) # Maybe with tuples?
True
>>> eq.eq_run
False
>>> {'eq': eq} == {'eq': eq} # dicts?
True
>>> eq.eq_run
False
>>> import numpy as np # Surely NumPy works as expected
>>> np.array([eq]) == np.array([eq])
True
>>> eq.eq_run
False
因此,似乎容器内的比较在Python中的工作方式不同。我希望对==
的调用会使用每个对象的__eq__
实现,否则有什么意义呢?另外
class TestableEq2(object):
def __init__(self):
self.eq_run = False
def __eq__(self, other):
self.eq_run = True
other.eq_run = True
return False
>>> eq = TestableEq2()
>>> [eq] == [eq]
True
>>> eq.eq_run
False
>>> eq == eq
False
>>> eq.eq_run
True
这是否意味着Python在容器的is
实现中使用__eq__
?有办法解决这个问题吗?
我的用例是我正在构建一个继承自某些collections
ABCs的数据结构,我想编写测试以确保我的结构正常运行。我认为注入比较时记录的值会很简单,但令我惊讶的是,检查时检查失败,以确保进行比较。
编辑:我应该提到这是在Python 2.7上,但我在3.3上看到了相同的行为。
答案 0 :(得分:14)
如果项目相同(==
),CPython的底层实现将跳过列表中项目的相等性检查(is
)。
CPython使用它作为优化,假设身份意味着相等。
这在PyObject_RichCompareBool中有记录,用于比较项目:
注意:如果o1和o2是同一个对象,PyObject_RichCompareBool()将始终为Py_EQ返回1,为Py_NE返回0。
来自listobject.c实施:
/* Search for the first index where items are different */
for (i = 0; i < Py_SIZE(vl) && i < Py_SIZE(wl); i++) {
int k = PyObject_RichCompareBool(vl->ob_item[i],
wl->ob_item[i], Py_EQ);
// k is 1 if objects are the same
// because of RichCmopareBool's behaviour
if (k < 0)
return NULL;
if (!k)
break;
}
正如您所见,只要RichCompareBool
为1
(True
),就不会检查这些项目。
从object.c执行PyObject_RichCompareBool
:
/* Quick result when objects are the same.
Guarantees that identity implies equality. */
if (v == w) {
if (op == Py_EQ)
return 1;
else if (op == Py_NE)
return 0;
}
// ... actually deep-compare objects
要覆盖此项,您必须手动比较这些项目。
答案 1 :(得分:10)
Python对序列相等性的测试如下:
Lists identical?
/ \
Y N
/ \
Equal Same length?
/ \
Y N
/ \
Items identical? Not equal
/ \
Y N
/ \
Equal Items equal?
/ \
Y N
/ \
Equal Not equal
如果两个序列长度相同但每个位置的项目不相同,则可以看到每个位置的项目的相等性仅 。如果要强制使用等式检查,则需要例如:
all(item1 == item2 for item1, item2 in zip(list1, list2))
答案 2 :(得分:4)
如果x is y
没有理由通过x == y
的合同拨打==
。 Python正在采用这种捷径。
可以通过在测试中创建eq1
和eq2
然后使用[eq1] == [eq2]
来验证/反驳这一点。
class TestableEq(object):
def __init__(self):
self.eq_run = False
def __eq__(self, other):
self.eq_run = True
return True # always assume equals for test
eq1 = TestableEq()
eq2 = TestableEq()
eq3 = TestableEq()
print [eq1] == [eq2] # True
print eq1.eq_run # True - implies e1 == e2
print eq2.eq_run # False - but NOT e2 == e1
print [eq3] == [eq3] # True
print eq3.eq_run # False - implies NO e3 == e3
如果项目为is
,则不会涉及==
。
与词典的区别可以类似地解释。
答案 3 :(得分:3)
比较两个列表时,cPython实现使用对象相等性(obj1 is obj2
)短路成员比较,因为,根据a comment in the code:
/* Quick result when objects are the same.
Guarantees that identity implies equality. */
如果这两个对象不是完全相同的对象,那么cPython会进行丰富的比较,如果实现则使用__eq__
。