Python 3.X的sorted()功能不能被依赖于排序异质序列,因为大多数对不同类型的是unorderable(数字类型喜欢int,float,decimal.Decimal等是一个例外):
sorted()
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()
相反,没有自然顺序的对象之间的比较是任意的,但在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的行为,我写了一个类来用作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))
用法示例:
>>> sorted(["one", 2.3, "four", -5], key=motley) [-5, 2.3, 'four', 'one']
到现在为止还挺好。
但是,当sorted(s, key=motley)某些包含复数的序列被调用时,我注意到了一个令人惊讶的行为:
sorted(s, key=motley)
>>> sorted([0.0, 1, (1+0j), False, (2+3j)], key=motley) [(1+0j), 0.0, False, (2+3j), 1]
我会期望的0.0,False并且1在一个组中(因为它们是可相互排序的),(1+0j)而(2+3j)在另一个组中(因为它们是同一类型)。这个结果中的复数不仅彼此分开,而且其中一个位于彼此可比较但不与之可比的一组对象中间,这一事实有些令人困惑。
0.0
False
(1+0j)
(2+3j)
这里发生了什么?
您不知道比较按照什么顺序进行,甚至不知道比较哪些项目,这意味着您真的不知道您__lt__将产生什么样的效果。您定义的内容__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出于基于类型名称的原因。
2+3j > False
结果是它排序1+0j到开头,然后离开2+3j它在False以上的位置。它甚至从未尝试将两个复数相互比较,并且将两者都比较的唯一项是1。
1+0j
2+3j
更一般而言,您的方法会导致不及物动的顺序,并为所涉及类型的名称提供适当的选择。例如,如果定义了类别A,B和C,使得A和C可以被比较,但比较B时的,然后通过创建对象它们引发异常a,b以及c(从相应的类),使得c < a,可以创建一个周期a < b < c < a。 a < b < c这是正确的,因为将根据名称对类进行比较,但是c < a由于可以直接比较这些类型。使用不及物动词的顺序,就不可能有“正确的”排序顺序。
A
B
C
a
b
c
c < a
a < b < c < a。 a < b < c
您甚至可以使用内置类型来执行此操作,尽管需要一些创意来思考类型名称按正确的字母顺序排列的对象:
>>> motley(1.0) < motley(lambda: 1) < motley(0) < motley(1.0) True
(因为'float' < 'function':-)
'float' < 'function'