Python 2.7自定义类__hash__和__eq__函数

时间:2018-04-04 01:14:10

标签: python python-2.7 hash line

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

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

我正在寻找一个解释为什么我在下面包含的测试代码中的前两行不相等,但第二组2行是相等的,尽管两组线都具有匹配的斜率值和y拦截?

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

2 个答案:

答案 0 :(得分:0)

原始line1line2 相等,因为舍入错误。

他们关闭,足够接近您的输出隐藏差异,但是如果您尝试打印数字的repr(例如,替换每个{} } {!r}},或者只是在格式中指定一堆乱七八糟的数字,您会发现他们的y截距值实际上是0.100000000000000090.09999999999999987。< / p>

有一篇名为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值的字符串作为字典键,或者您想要的任何内容。但有时候,值得构建一个包装器,通过将事物四舍五入到固定数量的位或数字来处理相等和散列 - 尽管这实际上并不能使舍入错误成为可能;它只是让它可以处理它们的某些输入集,所以它只有在你知道你的输入集时才有效。

答案 1 :(得分:0)

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

如果您创建的Fraction floatfrom 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') 不同)100%精确值:

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

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

fractions.Fraction

打印:

Fraction

1/10 x + 99/100也使用Point的规范化形式(正如您所见,它总是{{1}},即使输入有{{1}}个不同的{{1}} ,所以你的哈希码将会正常工作&#34;。