繁体   English   中英

测试Python中的实际相等性

[英]Test for actual equality in Python

我正在编写一个模拟特定库的Python2模块。 结果可能是floatintlongunicodestrtuplelist和自定义对象。 列表可能不包含列表,但可能包含元组。 元组可能不包含列表或元组。 否则,列表和元组可能包含上面列出的任何其他类型。

(实际上,该模块不应返回longstr ,但如果返回,则应分别与intunicode相比,将它们捕获并报告为不同。)

我正在编写一个测试程序,该程序根据模块尝试模仿的库的已知答案检查结果。 显而易见的答案是测试值和类型,但是我面临的一个问题是,在极端情况下,要测试的可能结果是-0.0 (应与0.0区别)和NaN (不是数字NaN浮点数可以取的值)。

然而:

>>> a = float('nan')
>>> b = float('nan')
>>> a == b
False
>>> c = float('-0.0')
>>> c
-0.0
>>> d = 1.0 - 1.0
>>> c == d
True

is运算符无济于事:

>>> a is b
False
>>> d is 0.0
False

repr帮助:

>>> repr(a) == repr(b)
True
>>> repr(c) == repr(d)
False
>>> repr(d) == repr(0.0)
True

但这只是一点,因为它对对象没有帮助:

>>> class e:
...   pass
... 
>>> f = e()
>>> g = e()
>>> f.x = float('nan')
>>> g.x = float('nan')
>>> f == g
False
>>> repr(f) == repr(g)
False

这可以工作:

>>> repr(f.__dict__) == repr(g.__dict__)
True

但是它失败并带有元组和列表:

>>> h = [float('nan'), f]
>>> i = [float('nan'), g]
>>> h == i
False
>>> repr(h) == repr(i)
False
>>> repr(h.__dict__) == repr(i.__dict__)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__dict__'

看来我已经接近了,所以我需要知道:

  1. 有没有更简单的方法来检查是否具有转换为字符串的负担的实际相等性?
  2. 如果没有,我将如何比较包含对象的列表或元组?

编辑:明确地说,我需要的是一个完整的比较功能。 我的测试功能大致如下:

>>> def test(expression, expected):
...   actual = eval(expression)
...   if not reallyequal(actual, expected):
...     report_error(expression, actual, expected)

我的问题涉及的是trueequal()的外观。

编辑2:我已经找到Python标准模块unittest,但是不幸的是,没有一个检查涉及该用例,因此,如果我打算使用它,则应该使用self.assertTrue(reallyequal(actual, expected))

实际上,令我惊讶的是,很难进行单元测试,包括预期的NaN和嵌套在结果中的零。 我仍在使用repr解决方案,它是半解决方案,但我愿意接受其他想法。

这是一个实现:

def really_equal(actual, expected, tolerance=0.0001):
    """Compare actual and expected for 'actual' equality."""

    # 1. Both same type?
    if not isinstance(actual, type(expected)):
        return False

    # 2. Deal with floats (edge cases, tolerance)
    if isinstance(actual, float):
        if actual == 0.0:
            return str(actual) == str(expected)
        elif math.isnan(actual):
            return math.isnan(expected)
        return abs(actual - expected) < tolerance

    # 3. Deal with tuples and lists (item-by-item, recursively)
    if isinstance(actual, (tuple, list)):
        return all(really_equal(i1, i2) for i1, i2 in zip(actual, expected))

    # 4. Fall back to 'classic' equality
    return actual == expected

您从“经典”平等中获得的一些优势案例:

>>> float('nan') == float('nan')
False
>>> really_equal(float('nan'), float('nan'))
True

>>> 0.0 == -0.0
True
>>> really_equal(0.0, -0.0)
False

>>> "foo" == u"foo"
True
>>> really_equal("foo", u"foo")
False

>>> 1L == 1
True
>>> really_equal(1L, 1)
False

类应该实现自己的__eq__ “魔术方法”,以确定两个实例是否相等-它们将落入# 4并在那里进行比较:

>>> class Test(object):

    def __init__(self, val):
        self.val = val

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


>>> a = Test(1)
>>> b = Test(1)
>>> really_equal(a, b)
True

从答案和评论中可以明显看出,我第一个问题的答案是(没有比使用repr()更简单的方法?),没有,没有更简单的方法。 因此,我对如何尽可能简单地完成此操作进行了更多研究,并且提出了解决方案,它回答了我的第二个问题。

repr()在大多数情况下都起作用,但是在自定义类的对象上失败。 由于自定义对象的默认repr()无论如何对于任何有意义的目的都没有用,所以我要做的是重写每个基类的__repr__方法,如下所示:

class MyClass:
    def __repr__(self):
        return self.__class__.__name__ + "(" \
            + repr(sorted(self.__dict__.items(), key=lambda t: t[0])) + ")"

现在,我可以在任何值上使用repr()并获得一个实际上唯一表示这些值的表达式,我的测试程序可以捕获该表达式。

def reallyequal(actual, expected):
    return repr(actual) == repr(expected)

(由于其简单性,我实际上将其嵌入到测试函数中)。

它在起作用:

>>> reallyequal(-0.0, 0.0)
False
>>> reallyequal(float('nan'),float('nan'))
True
>>> f = MyClass()
>>> f.x = float('nan')
>>> g = MyClass()
>>> g.x = float('nan')
>>> reallyequal(f, g)
True
>>> h = [f,3]
>>> i = [g,4]
>>> reallyequal(h, i)
False
>>> i[1] = 3
>>> reallyequal(h, i)
True
>>> g.x = 1
>>> reallyequal(h, i)
False
>>> f.x = 1L
>>> reallyequal(h, i)
False
>>> f.x = 1
>>> reallyequal(h, i)
True

编辑:编辑以将评论者的建议重新表示结果与__dict__合并。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM