【问题标题】:Python 3 sorting ignores __lt__ and __eq__Python 3 排序忽略 __lt__ 和 __eq__
【发布时间】:2016-06-18 14:06:58
【问题描述】:

我在 64 位窗口上使用 Python 3.5.1。我的问题是 Python 在排序时似乎忽略了用户定义类上的 __eq____lt__ 运算符。尝试对包含此类实例的元组进行排序时,使用自定义排序键是一种无效的解决方法

例子:

class Symbol:
  def __init__(self, name, is_terminal = False):
    self.name = name
    self.is_terminal = is_terminal
  def __eq__(self, other):
    return (self.is_terminal, self.name) == (other.is_terminal, other.name)
  def __lt__(self, other):
    return (self.is_terminal, self.name) < (other.is_terminal, other.name)

symbols = set()
for s in "abcdef":
  symbols.add(Symbol(s))

sorted_symbols = sorted(symbols)
# sorted_symbols now contain the symbols in random order

使用 functools.total_ordering 装饰器没有帮助

我的问题是如何在 Python 3 中定义用户类的排序?

【问题讨论】:

  • 排序绝对使用这些方法。你如何确定它们没有被使用?为什么排序键不是解决方法? key=lambda s: (s.is_terminal, s.name) 会给你同样的结果。
  • 预期的结果是什么?你会得到什么?
  • 您的 Symbol 类是可排序的,但不能像所写的那样可散列。如果定义__eq__,还需要定义__hash__,否则不能将实例用作字典键或将它们添加到集合中。如果您将symbols 更改为列表(并在循环中使用append 而不是add),您的代码就可以正常工作。

标签: python sorting python-3.x


【解决方案1】:

Python确实不会忽略__eq____lt__,至少如果您实际使用过@functools.total_ordering,则不会:

>>> from functools import total_ordering
>>> @total_ordering
... class Symbol:
...     def __init__(self, name, is_terminal=False):
...         self.name = name
...         self.is_terminal = is_terminal
...     def __repr__(self):
...         return 'Symbol({0.name!r}, is_terminal={0.is_terminal!r})'.format(self)
...     def __hash__(self):
...         return hash(self.name) ^ hash(self.is_terminal)
...     def __eq__(self, other):
...         print('{} __eq__ {}'.format(self, other))
...         return (self.is_terminal, self.name) == (other.is_terminal, other.name)
...     def __lt__(self, other):
...         print('{} __lt__ {}'.format(self, other))
...         return (self.is_terminal, self.name) < (other.is_terminal, other.name)
...
>>> symbols = set()
>>> for s in "abcdef":
...     symbols.add(Symbol(s))
...
>>> sorted(symbols)
Symbol('f', is_terminal=False) __lt__ Symbol('c', is_terminal=False)
Symbol('a', is_terminal=False) __lt__ Symbol('f', is_terminal=False)
Symbol('a', is_terminal=False) __lt__ Symbol('f', is_terminal=False)
Symbol('a', is_terminal=False) __lt__ Symbol('c', is_terminal=False)
Symbol('b', is_terminal=False) __lt__ Symbol('c', is_terminal=False)
Symbol('b', is_terminal=False) __lt__ Symbol('a', is_terminal=False)
Symbol('d', is_terminal=False) __lt__ Symbol('c', is_terminal=False)
Symbol('d', is_terminal=False) __lt__ Symbol('f', is_terminal=False)
Symbol('e', is_terminal=False) __lt__ Symbol('c', is_terminal=False)
Symbol('e', is_terminal=False) __lt__ Symbol('f', is_terminal=False)
Symbol('e', is_terminal=False) __lt__ Symbol('d', is_terminal=False)
[Symbol('a', is_terminal=False), Symbol('b', is_terminal=False), Symbol('c', is_terminal=False), Symbol('d', is_terminal=False), Symbol('e', is_terminal=False), Symbol('f', is_terminal=False)]

事实上,即使没有@total_ordering 也可以进行排序,因为 TimSort 实现只使用了__lt__;这是explicitly document

此方法对列表进行就地排序,仅使用

排序键也是一个选项,只需从键中返回(is_terminal, name) 元组:

>>> sorted(symbols, key=lambda s: (s.is_terminal, s.name))
[Symbol('a', is_terminal=False), Symbol('b', is_terminal=False), Symbol('c', is_terminal=False), Symbol('d', is_terminal=False), Symbol('e', is_terminal=False), Symbol('f', is_terminal=False)]

请注意,现在永远不会调用 __lt__ 方法,因为使用的是排序键。

【讨论】:

    猜你喜欢
    • 2013-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-07
    • 1970-01-01
    • 2012-01-23
    • 2011-10-18
    • 1970-01-01
    相关资源
    最近更新 更多