使Python用户自定义类可排序、可哈希化

130

在Python中定义用户自定义类使其可排序和/或可哈希时,需要重写/实现哪些方法?

需要注意哪些陷阱?

我在解释器中键入dir({})以获取内置字典的方法列表。其中,我假设需要实现其中的一些子集。

['__cmp__', '__eq__', '__ge__', '__gt__', '__hash__', '__le__', '__lt__', '__ne__']

Python3和Python2在需要实现哪些方法方面有区别吗?


3
这里有一个好的讨论:https://dev59.com/-XNA5IYBdhLWcg3wL6oc。Python 2.x和3.x之间的区别是删除了__cmp__ - Zach Kelling
4个回答

145

我差点把这个作为对其他答案的评论,但它本身就是一个答案。

要使你的项可排序,它们只需要实现__lt__方法。这是内置排序所使用的唯一方法。

其他比较或functools.total_ordering只有在实际想要使用比较运算符与类一起使用时才需要。

要使您的项可哈希,您需要像其他人指出的那样实现__hash__。您还应以兼容的方式实现__eq__ -- 相等的项应该具有相同的哈希值。


那么一个糟糕的__lt__实现可能会导致Python排序不可预测吗?(例如,如果x.lt(y)和y.lt(x)) - Matt Fenwick
6
“不可预测”这个词我不太确定,如果输入完全相同,那么结果会是一致的,但不同的输入顺序可能会导致不同的项排列顺序。如果排序时实现比较函数错误,那么Python就会错误地进行排序。我建议使用一个__key__函数将实例转换为元组,然后让__lt__ (self.__key__() < other.__key__())和__hash__ (hash(self.__key__()))都使用这个函数。 - agf
把类本身变为可排序的,而不仅仅是类的实例,怎么样?例如,如果你想说 FooClass < BarClass = False 但是 BarClass < FooClass = True。这样你就可以对一个类列表进行排序。 - odigity
1
@odigity 你需要在元类/类型上定义方法。 - agf
@agf - 这正是我担心的。好吧,我将去取那些被禁止的书籍... - odigity

35

Python 2和3之间没有任何区别。

为了排序:

你应该定义比较方法。这使得你的项目可以进行排序。通常情况下,不应该使用__cmp__()

我通常使用functools.total_ordering装饰器。

  

functools.total_ordering(cls) 给定一个定义了一个或多个丰富比较排序方法的类,此类装饰器提供其余部分。 这简化了指定所有可能丰富比较操作所涉及的工作:

  

该类必须定义 __lt__()__le__()__gt__()__ge__() 中的一个。此外,该类应提供一个__eq__()方法。

你应该注意你的比较方法没有任何副作用(即不改变对象中的任何值)。

为了哈希:

你应该实现__hash__()方法。我认为最好的方法是返回hash(repr(self)),这样你的哈希将是唯一的。


2
有关functools.total_ordering的示例,请参见此处文档。 - Evgeni Sergeev
根据您的使用情况,考虑返回id(self)哈希值,因为它将是本地内存位置。这是一个快速且基本稳定的哈希,实现了哈希,以便如果对象是另一个对象,则哈希相同,否则不同。 - Tatarize

6

有几种方法可以标记您的对象可排序。首先是通过一组函数定义的丰富比较:

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

还可以只定义一个函数:

object.__cmp__(self, other)

如果你想定义自己的__hash__函数,可以定义最后一个参数。请参阅文档


9
在Python 3中,"[...] __cmp __()特殊方法不再受支持",请参见此处的相关部分 - Evgeni Sergeev

-6

实现__lt__(self,other)方法是使您的类可排序的答案。
它不仅可以用于内置方法sorted(iterable),还可以通过heapq模块用于优先队列。

此外,我不喜欢Python的设计,太多的'__ge__', '__gt__', '__le__', '__lt__', '__ne__'方法一点也不直观
相比之下,Java的Interface Comparable<T>(请参见java doc)返回负整数、零或正整数,表示此对象小于、等于或大于指定对象,这是直接和友好的


13
大多数您的回答涵盖了您的观点(这不应该是回答的一部分)。 - skyking
3
@skyking...虽然我不同意这个回答中的观点,但我认为带有相关研究和有用数据的观点是非常有价值的。这个回答中的错误在于没有用相关数据支持观点。但理解偏好可以帮助人们做出决策,因此非常有用。在这里,回答的形式不佳,因为没有提供有用且可操作的Python代码来说明作者偏好的Python编程方式。 - Andrew
3
@Andrew 除了这个观点完全无关紧要,因为问题是关于Python的。当问题不是“哪种语言更适合做这件事?”而是关于Python时,告诉Java更好没有意义。这几乎是在钓鱼,讨论永无止境的毫无意义的“什么是最好的编程语言?”争论,对于这个问题是如此无用。 - Welgriv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接