为什么这个用于排序异构序列的关键类表现奇怪?

Zer*_*eus 8 python sorting python-2.x complex-numbers python-3.x

蟒3.X的sorted()功能不能依赖于异质序列进行排序,因为大多数对不同类型的是unorderable(数字类型,如int,float,decimal.Decimal等是一个例外):

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()
Run Code Online (Sandbox Code Playgroud)

相比之下,没有自然顺序的对象之间的比较是任意的,但在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']
Run Code Online (Sandbox Code Playgroud)

为了复制的Python 2.x中的在Python 3.x的行为,我写了一个类的使用key参数sorted(),这依赖于这样一个事实sorted()保证只使用低于比较:

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))
Run Code Online (Sandbox Code Playgroud)

用法示例:

>>> sorted(["one", 2.3, "four", -5], key=motley)
[-5, 2.3, 'four', 'one']
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

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

>>> sorted([0.0, 1, (1+0j), False, (2+3j)], key=motley)
[(1+0j), 0.0, False, (2+3j), 1]
Run Code Online (Sandbox Code Playgroud)

我本来期望的0.0,False并且1在一个组中(因为它们是可相互订购的),(1+0j)(2+3j)在另一个组中(因为它们属于同一类型).事实上,这个结果中的复数不仅彼此分开,而且其中一个位于一组彼此相当但不与之相当的物体的中间,这有点令人困惑.

这里发生了什么?

Bre*_*arn 7

你不知道比较的顺序是什么,甚至比较哪些项目,这意味着你无法真正知道你的__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
Run Code Online (Sandbox Code Playgroud)

然后:

>>> 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]
Run Code Online (Sandbox Code Playgroud)

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

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

更一般地说,您的方法可以导致不及物的排序,并对所涉及的类型的名称进行适当的选择.例如,如果定义了类别A,B和C,使得A和C可以被比较,但它们比较B时的,然后通过创建对象引发异常a,b以及c(从相应的类),使得c < a,可以创建一个循环a < b < c < a. a < b < c将是真实的,因为将根据名称对类进行比较,但c < a由于可以直接比较这些类型.使用不及物动词排序,没有"正确"排序顺序的希望.

您甚至可以使用内置类型执行此操作,但需要有点创意才能考虑类型名称按字母顺序排列的对象:

>>> motley(1.0) < motley(lambda: 1) < motley(0) < motley(1.0)
True
Run Code Online (Sandbox Code Playgroud)

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