【问题标题】:Python 2.7 custom class __hash__ and __eq__ functionsPython 2.7 自定义类 __hash__ 和 __eq__ 函数
【发布时间】:2018-09-13 10:32:17
【问题描述】:

我遇到了一个问题,我认为这与自定义类 __eq__/__hash__ 函数的不当实现有关。

我创建了一个自定义 Line 类,其中一条线包含一个斜率和 y 截距,它们是从 2 个点计算得出的。我正在对两行之间的相等性进行测试,这会产生如下所示的意外结果。

我正在寻找解释为什么我在下面包含的测试代码中的前 2 行不相等,但第 2 组 2 行是相等的,尽管两组行的斜率和你拦截?

class Point:
    def __init__(self, x1, y1):
        self.x = x1
        self.y = y1

    def to_string(self):
        return '{},{}'.format(self.x, self.y)

class Line:
    def __init__(self, pt1, pt2):
        self.m = (pt1.y - pt2.y)/(pt1.x - pt2.x)
        self.b = pt1.y - self.m * pt1.x

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.m == other.m and self.b == other.b
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash((self.m, self.b))

    def print_line(self):
        print('y = {} x + {}'.format(self.m, self.b))

测试代码:

pt_a = Point(0.1, 1.0)
pt_b = Point(1.1, 1.1)
pt_c = Point(2.1, 1.2)
line1 = Line(pt_a, pt_b)
print('line1:')
line1.print_line()
line2 = Line(pt_b, pt_c)
print('line2:')
line2.print_line()
if line1 == line2:
    print('lines equal')
else:
    print('lines not equal')

pt_x = Point(0.5, 1)
pt_y = Point(1.5, 2)
pt_z = Point(2.5, 3)
line1 = Line(pt_x, pt_y)
print('line1:')
line1.print_line()
line2 = Line(pt_y, pt_z)
print('line2:')
line2.print_line()
if line1 == line2:
    print('lines equal')
else:
    print('lines not equal')

这个测试产生输出:

line1:
y = 0.1 x + 0.99
line2:
y = 0.1 x + 0.99
lines not equal
line1:
y = 1.0 x + 0.5
line2:
y = 1.0 x + 0.5
lines equal

【问题讨论】:

  • 我不是在比较 Points,Lines 不会存储它们创建时使用的 Points。
  • 您依赖于浮点舍入误差是一致的还是没有发生。
  • 这个问题不仅仅是通常的“浮点数不精确”问题,因为 OP 正在编写 __hash__ 方法,这意味着他不能只使用 isclose 或等效的.
  • 旁注:如果你想让其他类使用你的Lines,你应该return NotImplemented,而不是return False,当你不认识other的类型时在__eq__。而您的__ne__ 应该是ret = self.__eq__(other)return ret if ret is NotImplemented else not ret(Python 2 上__ne__ 的规范主体,以根据__eq__ 正确实现它;Python 3 会自动正确地执行此操作)。

标签: python python-2.7 hash line


【解决方案1】:

由于舍入错误,您原来的 line1line2相等。

它们接近,足够接近以至于您的输出隐藏了差异,但如果您尝试打印数字的repr(例如,将每个{} 替换为{!r}) ,或者只是在您的格式中指定一大堆数字,您会看到它们的 y 截距值实际上是 0.100000000000000090.09999999999999987

有一篇名为What Every Computer Scientist Should Know About Floating Point 的著名论文非常重要,它已通过引用并入各种标准文档中。 (我看到一些答案链接指向一个类似名称的网站,What Every Programmer Should Know About Floating Point,看起来它可能更友好,但我不能保证它的准确性。)

无论如何,一般来说,处理这个问题的正确方法是使用math.isclose。当然,如果你想学习 Python 2.7,即使是 2018 年,你也不能这样做,因为它没有这样的东西。 PEP 485 包含算法的伪代码描述,以及纯 Python 实现的链接。

但是在这种特定情况下,您必须考虑一个问题:这些值实际上并不相等,因此它们的哈希值不应相同。这会破坏您的预期设计吗?通常,答案是您的设计不应该使用线条,或任何其他具有float 值的东西,作为字典键,或任何您想要的东西。但有时,值得构建一个包装器,通过将事物四舍五入到固定数量的位或数字来处理相等和散列——尽管这实际上并没有使舍入错误成为不可能;它只是使处理某些输入集成为可能,因此只有在您知道输入集时才有效。

【讨论】:

    【解决方案2】:

    使用浮点数以不同方式计算相同的逻辑结果不会得到一致的结果。对于这样的高精度值,您可能希望使用无限精度的数值类型,例如 fractions.Fraction

    如果您使用 Fraction 创建您的 Points,但(与 float 不同)100% 精确值:

    from fractions import Fraction
    pt_a = Point(Fraction(1, 10), Fraction(1))
    pt_b = Point(Fraction(11, 10), Fraction(11, 10))
    pt_c = Point(Fraction(21, 10), Fraction(12, 10))
    

    然后您的代码按预期工作:

    line1 = Line(pt_a, pt_b)
    print('line1:')
    line1.print_line()
    line2 = Line(pt_b, pt_c)
    print('line2:')
    line2.print_line()
    if line1 == line2:
        print('lines equal')
    else:
        print('lines not equal')
    

    哪个打印:

    line1:
    y = 1/10 x + 99/100
    line2:
    y = 1/10 x + 99/100
    lines equal
    

    fractions.Fraction 也使用Fraction 的规范化形式(如您所见,它始终为1/10 x + 99/100,即使输入不同的Points),因此您的哈希代码将“正常工作”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-14
      • 2011-03-05
      • 2015-08-01
      • 2016-07-14
      • 2017-12-23
      • 2016-11-01
      • 2020-08-28
      • 1970-01-01
      相关资源
      最近更新 更多