在Python 3中按字典序排序深层嵌套的混合数据类型列表
在Python 3中,list.sort()
这个方法会按照字典顺序进行排序。不过在Python 3里,如果你把一个列表和一个float
(浮点数)或int
(整数)进行比较,就会出现TypeError
的错误。这和Python 2不一样,Python 2里是可以这样做的:
>>> [0, 1] < 2
False
那么,有什么好的办法可以让它像Python 2那样工作呢?
我试过创建一个list
的子类,但这样做的话,每个嵌套的列表都必须转换成这个子类的类型,这样所有的嵌套比较才能使用我重写的比较方法。有没有什么办法可以做到这一点,而不需要递归地把每个嵌套列表都转换成子类呢?
我希望能够这样比较两个列表:
>>> a = [[[0, 1], [2, 3]], [0, 1]]
>>> b = [[0, 1], [2, 3]]
>>> a < b
False
结果应该是False
,因为a[0][0]
是一个list
(列表),而b[0][0]
是一个int
(整数),在我的情况下,int
应该总是被认为小于list
。
编辑:
我想实现一个排序函数,它的功能和Python 3内置的list.sort
完全一样,唯一的不同是当一个list
和一个float
或int
进行比较时,这个list
应该总是被认为更大。
3 个回答
这里有一种比较慢的方法。
如果你想在两个不能直接比较的类型 A
和 B
之间建立顺序,可以把它们的实例放在一个元组里:
a = [[[0, 1], [2, 3]], [0, 1]]
b = [[0, 1], [2, 3]]
def deep_annotate(item):
if isinstance(item, list):
return (1, [deep_annotate(i) for i in item])
else:
return (0, item)
deep_annotate(a) < deep_annotate(b)
#>>> False
deep_annotate(a) > deep_annotate(b)
#>>> True
不过,这种方法很多时候效率不高,可以通过巧妙使用 cmp_to_key
来提高效率。
正确的做法不是去继承 list
,而是直接使用 排序方法中的 key
参数,来定义一个自定义的关键函数:
sorted(l, key=custom_key_function)
custom_key_function(list_element)
应该为每个列表中的元素生成一个标准化的关键值,确保所有的关键值都是同一种类型。
由于我不知道你的列表可能包含什么样的元素,所以不方便进一步讨论具体的实现细节。不过,从你的例子来看,可能需要使用相同的 custom_key_function
来递归地排序子列表。
根据Python 2 的文档,
大多数内置类型的对象在比较时,如果它们不是同一个对象,通常会被认为是不相等的;至于一个对象被认为比另一个对象小或大,这个判断是随意的,但在程序的一次执行中是保持一致的。
对象的比较只有在两个对象类型相同的时候才有意义。在程序中不应该依赖像 [0, 1] < 2
这样的表达式返回的值,这也是为什么这个行为在 Python 3 中被去掉了。
进一步解释一下,如果你有一个列表 [[[0, 1], [2, 3]], [0, 1]]
,它有两个元素:
[[0, 1], [2, 3]] 和 [0, 1]
。为了让 Python 对它们进行排序,它会比较它们内部的值,按照字典序进行比较,因为第一个是包含 [0, 1] 和 [2, 3]
的列表,第二个是包含 0 和 1
的列表。但是,接下来它需要比较 [0, 1] 和 0
,这两个类型不同,因此比较的结果是随意的。
所以,这种排序是有问题的。
说到这里,如果你有一些可以有意义地排序的列表,还有一些不能排序的(因为上面的原因),一个简单的解决办法是捕获可能出现的异常,然后返回 False。
try:
[0, 1] < 2
except TypeError:
# return or assign False. True is not actually meaningful.
或者,对于 list.sort() 方法
try:
x.sort()
except TypeError:
pass # Do nothing. Python would produce meaningless results, anyway.
如果你想要进行有意义的排序(如果这确实有意义的话),那么你需要定义一个键函数,正如之前提到的那样。不过,这可能会比较复杂。也许从不同的角度看待你的问题会更好。