Python 2 assertItemsEqual 结果不正确

Python 2 assertItemsEqual incorrect result

我运行在Python2下使用unittest.TestCase.assertItemsEqual函数进入一个有趣的情况;在这里发布我的发现以供后代使用。

以下单元测试在 Python 2 应该成功时中断:

import unittest

class Foo(object):
    def __init__(self, a=1, b=2):
        self.a = a
        self.b = b
    def __repr__(self):
        return '({},{})'.format(self.a, self.b)
    def __eq__(self, other):
        return self.a == other.a and self.b == other.b
    def __lt__(self, other):
        return (self.a, self.b) < (other.a, other.b)

class Test(unittest.TestCase):
    def test_foo_eq(self):
        self.assertEqual(sorted([Foo()]), sorted([Foo()]))
        self.assertItemsEqual([Foo()], [Foo()])

unittest.main()

这是输出:

======================================================================
FAIL: test_foo_eq (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tsanders/scripts/one_offs/test_unittest_assert_items_equal2.py", line 17, in test_foo_eq
    self.assertItemsEqual([Foo()], [Foo()])
AssertionError: Element counts were not equal:
First has 1, Second has 0:  (1,2)
First has 0, Second has 1:  (1,2)

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

这很令人困惑,因为 docs 状态:

It [assertItemsEqual] is the equivalent of assertEqual(sorted(expected), sorted(actual)) but it works with sequences of unhashable objects as well.

相同的测试在 Python 3 下通过(将 self.assertItemsEqual 替换为 self.assertCountEqual,因为名称已更改)。

编辑:发布这个问题后,我确实找到了 this 其他问题,涵盖了既没有定义 __eq__ 也没有定义 __hash__ 的情况。

为了让测试通过 Python 2 和 3,我必须将行 __hash__ = None 添加到 Foo

assertItemsEqual/assertCountEqual 函数采用不同的代码路径,具体取决于每个列表中的项目是否可散列。并根据 docs:

If a class does not define a __cmp__() or __eq__() method it should not define a __hash__() operation either; if it defines __cmp__() or __eq__() but not __hash__(), its instances will not be usable in hashed collections.

考虑到这一点,当定义 __eq__ 时,Python 2 和 3 对于 __hash__ 有不同的行为:

  • 在 Python 2 中,定义 __eq__ 不会影响默认提供的 __hash__,但尝试在散列容器中使用该项目会导致实现定义的行为(例如键可能会重复,因为它们具有不同的哈希值,即使 __eq__ 会 return True).
  • 在Python 3中,定义__eq____hash__设置为None

从 Python 2.6 开始,__hash__ 可以显式设置为 None 以使 class 不可散列。在我的例子中,需要才能让assertItemsEqual使用依赖于__eq__而不是__hash__的正确比较算法。