python - 为什么这个用于对异构序列进行排序的关键类表现得很奇怪?

标签 python python-3.x sorting python-2.x complex-numbers

Python 3.x 的 sorted()不能依赖函数对异构序列进行排序,因为大多数不同类型对是不可排序的(数字类型,如 intfloatdecimal.Decimal等是一个异常(exception)):

Python 3.4.2 (default, Oct  8 2014, 08:07:42) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> sorted(["one", 2.3, "four", -5])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: float() < str()

相比之下,没有自然顺序的对象之间的比较是任意的,但在 Python 2.x 中是一致的,因此 sorted() 有效:

Python 2.7.8 (default, Aug  8 2014, 14:55:30) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> sorted(["one", 2.3, "four", -5])
[-5, 2.3, 'four', 'one']

为了在 Python 3.x 中复制 Python 2.x 的行为,我编写了一个类用作 sorted()key 参数,它依赖于sorted()guaranteed 的事实仅使用小于比较:

class motley:

    def __init__(self, value):
        self.value = value

    def __lt__(self, other):
        try:
            return self.value < other.value
        except TypeError:
            return repr(type(self.value)) < repr(type(other.value))

示例用法:

>>> sorted(["one", 2.3, "four", -5], key=motley)
[-5, 2.3, 'four', 'one']

到目前为止,还不错。

但是,当使用包含复数的特定序列调用sorted(s, key=motley) 时,我注意到一个令人惊讶的行为:

>>> sorted([0.0, 1, (1+0j), False, (2+3j)], key=motley)
[(1+0j), 0.0, False, (2+3j), 1]

我原以为 0.0False1 属于一组(因为它们是可相互排序的),而 (1+0j)(2+3j) 在另一个(因为它们是同一类型)。这个结果中的复数不仅彼此分离,而且其中一个复数位于一组可相互比较但不可与之比较的对象的中间,这一事实有些令人费解。

这是怎么回事?

最佳答案

您不知道比较的顺序是什么,甚至不知道比较了哪些项目,这意味着您无法真正知道您的 __lt__ 会产生什么影响。会有。您定义的 __lt__有时取决于实际值,有时取决于类型的字符串表示形式,但在排序过程中,这两种版本都可能用于同一对象。这意味着您的排序不仅仅由列表中的对象决定,还可能取决于它们的初始顺序。这反过来意味着仅仅因为对象可以相互比较并不意味着它们会被放在一起;他们之间可能被一个无与伦比的物体“挡住了”。

您可以通过放入一些调试打印来查看正在比较的内容,从而了解正在发生的事情:

class motley:

    def __init__(self, value):
        self.value = value

    def __lt__(self, other):
        fallback = False
        try:
            result = self.value < other.value
        except TypeError:
            fallback = True
            result = repr(type(self.value)) < repr(type(other.value))
        symbol = "<" if result else ">"
        print(self.value, symbol, other.value, end="")
        if fallback:
            print(" -- because", repr(type(self.value)), symbol, repr(type(other.value)))
        else:
            print()
        return result

然后:

>>> sorted([0.0, 1, (1+0j), False, (2+3j)], key=motley)
1 > 0.0
(1+0j) < 1 -- because <class 'complex'> < <class 'int'>
(1+0j) < 1 -- because <class 'complex'> < <class 'int'>
(1+0j) < 0.0 -- because <class 'complex'> < <class 'float'>
False > 0.0
False < 1
(2+3j) > False -- because <class 'complex'> > <class 'bool'>
(2+3j) < 1 -- because <class 'complex'> < <class 'int'>
[(1+0j), 0.0, False, (2+3j), 1]

例如,您可以看到基于类型的排序用于将复数与 1 进行比较,但不用于比较 1 和 0。同样 0.0 < False出于“正常”原因,但是 2+3j > False出于基于类型名称的原因。

结果是排序1+0j到开头,然后离开2+3j它高于 False 的地方。它甚至从不尝试将两个复数相互比较,并且将它们都比较的唯一项目是 1。

更一般地说,您的方法可以通过对所涉及类型的名称进行适当的选择来实现不及物排序。例如,如果您定义类 A、B 和 C,这样可以比较 A 和 C,但在与 B 比较时它们会引发异常,那么通过创建对象 a , bc (来自各自的类(class))使得c < a , 你可以创建一个循环 a < b < c < a . a < b < c将是真的,因为类将根据它们的名称进行比较,但是 c < a因为可以直接比较这些类型。对于不及物排序,不可能有“正确”的排序顺序。

你甚至可以使用内置类型来做到这一点,尽管它需要一点创意来思考类型名称按正确字母顺序排列的对象:

>>> motley(1.0) < motley(lambda: 1) < motley(0) < motley(1.0)
True

(因为 'float' < 'function' :-)

关于python - 为什么这个用于对异构序列进行排序的关键类表现得很奇怪?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26567667/

相关文章:

python - 保存上传的 Base64 数据会出现 TypeError : a bytes-like object is required, 而不是 'str'

python - 如何在尚不存在的键下创建嵌套字典?

python - Python中的链式方法调用缩进样式

python - 将一个类的对象添加到另一个类的对象并在 Python 中检查重复项

python - 根据 Pandas 中的列表对多列进行排序

java - 比较具有多个属性的 bean,每个属性取决于两个 bean

php - 查询对mysql中包含逗号分隔值的列进行排序

python - 跟踪 Python 中重复列表中的值变化

python-3.x - 奇怪的 Pandas 日期切片行为(不切片日期)

python - webhelpers 有一些严重错误