简体   繁体   English

测试Python中的实际相等性

[英]Test for actual equality in Python

I am writing a Python2 module that emulates a certain library. 我正在编写一个模拟特定库的Python2模块。 The results may be float , int , long , unicode , str , tuple , list , and custom objects. 结果可能是floatintlongunicodestrtuplelist和自定义对象。 Lists may not contain lists, but they may contain tuples. 列表可能不包含列表,但可能包含元组。 Tuples may not contain lists or tuples. 元组可能不包含列表或元组。 Otherwise, lists and tuples may contain any of the other types listed above. 否则,列表和元组可能包含上面列出的任何其他类型。

(Actually, the module should not return long or str , but if it does, they should be caught and reported as different when compared to int and unicode , respectively.) (实际上,该模块不应返回longstr ,但如果返回,则应分别与intunicode相比,将它们捕获并报告为不同。)

I am writing a testing program that checks the results against known answers by the library my module tries to emulate. 我正在编写一个测试程序,该程序根据模块尝试模仿的库的已知答案检查结果。 The obvious answer would be to test the values and the types, but one problem I'm facing is that in corner cases, possible results to test for are -0.0 (which should be distinguished from 0.0 ) and NaN (Not a Number - a value a float can take). 显而易见的答案是测试值和类型,但是我面临的一个问题是,在极端情况下,要测试的可能结果是-0.0 (应与0.0区别)和NaN (不是数字NaN浮点数可以取的值)。

However: 然而:

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

The is operator doesn't help a bit: is运算符无济于事:

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

repr helps: repr帮助:

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

But only to a point, since it doesn't help with objects: 但这只是一点,因为它对对象没有帮助:

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

This works though: 这可以工作:

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

But it fails with tuples and lists: 但是它失败并带有元组和列表:

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

It seems I'm close, so I need to know: 看来我已经接近了,所以我需要知道:

  1. Is there a simpler way to check for actual equality that doesn't have the burden of converting to string? 有没有更简单的方法来检查是否具有转换为字符串的负担的实际相等性?
  2. If not, how would I go about comparing lists or tuples containing objects? 如果没有,我将如何比较包含对象的列表或元组?

Edit: To be clear, what I'm after is a full comparison function. 编辑:明确地说,我需要的是一个完整的比较功能。 My test function looks roughly like this: 我的测试功能大致如下:

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

My question concerns what should reallyequal() look like. 我的问题涉及的是trueequal()的外观。

Edit 2: I've found the Python standard module unittest but unfortunately none of the checks covers this use case, so it seems that if I intend to use it, I should use something like self.assertTrue(reallyequal(actual, expected)) . 编辑2:我已经找到Python标准模块unittest,但是不幸的是,没有一个检查涉及该用例,因此,如果我打算使用它,则应该使用self.assertTrue(reallyequal(actual, expected))

I'm actually surprised that it's so hard to make unit tests including expected NaNs and minus zeros nested within the results. 实际上,令我惊讶的是,很难进行单元测试,包括预期的NaN和嵌套在结果中的零。 I'm still using the repr solution which is a half-solution, but I'm open to other ideas. 我仍在使用repr解决方案,它是半解决方案,但我愿意接受其他想法。

Here is one implementation: 这是一个实现:

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

A few of your edge cases from "classic" equality: 您从“经典”平等中获得的一些优势案例:

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

Classes should implement their own __eq__ "magic method" to determine whether or not two instances are equal - they will fall through to # 4 and be compared there: 类应该实现自己的__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

From the answers and comments it seems clear that the answer to my first question (is there a simpler way than using repr() ?) is no, there is no simpler way. 从答案和评论中可以明显看出,我第一个问题的答案是(没有比使用repr()更简单的方法?),没有,没有更简单的方法。 So I've researched more on how to accomplish this as simply as possible and I've come up with this solution which answers my second question. 因此,我对如何尽可能简单地完成此操作进行了更多研究,并且提出了解决方案,它回答了我的第二个问题。

repr() works for the most part, but fails on objects of custom classes. repr()在大多数情况下都起作用,但是在自定义类的对象上失败。 Since the default repr() of a custom object is not useful as-is anyway for any meaningful purpose, what I've done is to override the __repr__ method of each base class like this: 由于自定义对象的默认repr()无论如何对于任何有意义的目的都没有用,所以我要做的是重写每个基类的__repr__方法,如下所示:

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

Now I can use repr() on any of the values and get an expression that actually represents these values uniquely, that my test program can catch. 现在,我可以在任何值上使用repr()并获得一个实际上唯一表示这些值的表达式,我的测试程序可以捕获该表达式。

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

(which I will actually embed in the test function due to its simplicity). (由于其简单性,我实际上将其嵌入到测试函数中)。

Here it is in action: 它在起作用:

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

Edit: Edited to incorporate commenter's suggestions re repr results with __dict__ . 编辑:编辑以将评论者的建议重新表示结果与__dict__合并。

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

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