如何在Python3.x中获得类似2.x的排序行为?

如何在Python3.x中获得类似2.x的排序行为?,python,python-3.x,sorting,python-2.x,Python,Python 3.x,Sorting,Python 2.x,我试图在3.x中复制(并且如果可能的话改进)Python2.x的排序行为,以便像int、float等相互可排序的类型按预期排序,并且在输出中对相互不可排序的类型进行分组 下面是我所说的一个例子: >>> sorted([0, 'one', 2.3, 'four', -5]) # Python 2.x [-5, 0, 2.3, 'four', 'one'] 然而,这并不是我想要的。首先,它打破了相互可排序类型的自然排序: >>> sorted([0, 123

我试图在3.x中复制(并且如果可能的话改进)Python2.x的排序行为,以便像
int
float
等相互可排序的类型按预期排序,并且在输出中对相互不可排序的类型进行分组

下面是我所说的一个例子:

>>> sorted([0, 'one', 2.3, 'four', -5])  # Python 2.x
[-5, 0, 2.3, 'four', 'one']
然而,这并不是我想要的。首先,它打破了相互可排序类型的自然排序:

>>> sorted([0, 123.4, 5, -6, 7.89])
[-6, 0, 5, 7.89, 123.4]
>>> sorted([0, 123.4, 5, -6, 7.89], key=motley)
[7.89, 123.4, -6, 0, 5]
>>> sorted([{1:2}, {3:4}], key=motley)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()
其次,当输入包含两个本质上不可排序类型相同的对象时,会引发异常:

>>> sorted([{1:2}, {3:4}], key=motley)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()
。。。它的工作原理如下:

>>> sorted([0, 'one', 2.3, 'four', -5], key=motley)
[-5, 0, 2.3, 'four', 'one']
。。。但没有考虑到可能存在其他相互可排序的不同(可能是用户定义的)类型,当然,对于本质上不可排序的类型仍然失败:

>>> sorted([0, 123.4, 5, -6, 7.89])
[-6, 0, 5, 7.89, 123.4]
>>> sorted([0, 123.4, 5, -6, 7.89], key=motley)
[7.89, 123.4, -6, 0, 5]
>>> sorted([{1:2}, {3:4}], key=motley)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()
排序([{1:2},{3:4}],key=motley) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:无序类型:dict()
有没有另一种方法既能解决任意、不同但相互有序类型的问题,又能解决本质上不可有序类型的问题?

愚蠢的想法:第一步,将所有不同的项目分成可以相互比较的组,对各个组进行排序,最后将它们连接起来。我假设一个项目可以与一个组的所有成员进行比较,前提是它可以与一个组的第一个成员进行比较。类似这样的东西(蟒蛇3):


这似乎也是一种“稳定排序”,因为组是按照遇到不可比较项的顺序形成的。

这个答案的目的是在Python3中忠实地重新创建Python2排序顺序的每个细节

实际的Python2实现相当复杂,但是在实例有机会实现正常的比较规则之后,最终的回退是否会发生。这是在给了单个类型一个比较的机会之后(通过
\uuuu cmp\uuuuuu
\uuuu lt\uuuuu
钩子)

在包装器中以纯Python的形式实现该函数,再加上模拟规则的异常(
dict
,特别是复数),我们可以在Python 3中获得相同的Python 2排序语义:

from numbers import Number


# decorator for type to function mapping special cases
def per_type_cmp(type_):
    try:
        mapping = per_type_cmp.mapping
    except AttributeError:
        mapping = per_type_cmp.mapping = {}
    def decorator(cmpfunc):
        mapping[type_] = cmpfunc
        return cmpfunc
    return decorator


class python2_sort_key(object):
    _unhandled_types = {complex}

    def __init__(self, ob):
       self._ob = ob

    def __lt__(self, other):
        _unhandled_types = self._unhandled_types
        self, other = self._ob, other._ob  # we don't care about the wrapper

        # default_3way_compare is used only if direct comparison failed
        try:
            return self < other
        except TypeError:
            pass

        # hooks to implement special casing for types, dict in Py2 has
        # a dedicated __cmp__ method that is gone in Py3 for example.
        for type_, special_cmp in per_type_cmp.mapping.items():
            if isinstance(self, type_) and isinstance(other, type_):
                return special_cmp(self, other)

        # explicitly raise again for types that won't sort in Python 2 either
        if type(self) in _unhandled_types:
            raise TypeError('no ordering relation is defined for {}'.format(
                type(self).__name__))
        if type(other) in _unhandled_types:
            raise TypeError('no ordering relation is defined for {}'.format(
                type(other).__name__))

        # default_3way_compare from Python 2 as Python code
        # same type but no ordering defined, go by id
        if type(self) is type(other):
            return id(self) < id(other)

        # None always comes first
        if self is None:
            return True
        if other is None:
            return False

        # Sort by typename, but numbers are sorted before other types
        self_tname = '' if isinstance(self, Number) else type(self).__name__
        other_tname = '' if isinstance(other, Number) else type(other).__name__

        if self_tname != other_tname:
            return self_tname < other_tname

        # same typename, or both numbers, but different type objects, order
        # by the id of the type object
        return id(type(self)) < id(type(other))


@per_type_cmp(dict)
def dict_cmp(a, b, _s=object()):
    if len(a) != len(b):
        return len(a) < len(b)
    adiff = min((k for k in a if a[k] != b.get(k, _s)), key=python2_sort_key, default=_s)
    if adiff is _s:
        # All keys in a have a matching value in b, so the dicts are equal
        return False
    bdiff = min((k for k in b if b[k] != a.get(k, _s)), key=python2_sort_key)
    if adiff != bdiff:
        return python2_sort_key(adiff) < python2_sort_key(bdiff)
    return python2_sort_key(a[adiff]) < python2_sort_key(b[bdiff])
如果您想精确地模拟Python 2的行为,可能需要添加更多的特殊情况

如果你想对复杂的数字进行排序,你需要将它们与非数字组一致地放在一起;e、 g:

# Sort by typename, but numbers are sorted before other types
if isinstance(self, Number) and not isinstance(self, complex):
    self_tname = ''
else:
    self_tname = type(self).__name__
if isinstance(other, Number) and not isinstance(other, complex):
    other_tname = ''
else:
    other_tname = type(other).__name__
一些测试用例:

>>> sorted([0, 'one', 2.3, 'four', -5], key=python2_sort_key)
[-5, 0, 2.3, 'four', 'one']
>>> sorted([0, 123.4, 5, -6, 7.89], key=python2_sort_key)
[-6, 0, 5, 7.89, 123.4]
>>> sorted([{1:2}, {3:4}], key=python2_sort_key)
[{1: 2}, {3: 4}]
>>> sorted([{1:2}, None, {3:4}], key=python2_sort_key)
[None, {1: 2}, {3: 4}]

这里没有运行Python3,但类似的东西可能会起作用。测试以查看对“值”执行“小于”比较是否会创建异常,然后执行“某些操作”来处理该情况,例如将其转换为字符串

当然,如果列表中有其他类型不是同一类型但可以相互排序,则仍然需要更特殊的处理

from numbers import Real
from decimal import Decimal

def motley(value):
    numeric = Real, Decimal
    if isinstance(value, numeric):
        typeinfo = numeric
    else:
        typeinfo = type(value)

    try:
        x = value < value
    except TypeError:
        value = repr(value)

    return repr(typeinfo), value

>>> print sorted([0, 'one', 2.3, 'four', -5, (2+3j), (1-3j)], key=motley)
[-5, 0, 2.3, (1-3j), (2+3j), 'four', 'one']
从数字导入实数
从十进制输入十进制
def杂色(值):
数字=实数,十进制
如果isinstance(值,数字):
类型信息=数字
其他:
类型信息=类型(值)
尝试:
x=值<值
除类型错误外:
值=repr(值)
返回repr(typeinfo),值
>>>打印排序([0,'1',2.3,'4',-5,(2+3j),(1-3j)],键=杂色)
[-5,0,2.3,(1-3j),(2+3j),‘四’,‘一’]

为了避免使用异常和基于类型的解决方案,我提出了以下建议:

#! /usr/bin/python3

import itertools

def p2Sort(x):
    notImpl = type(0j.__gt__(0j))
    it = iter(x)
    first = next(it)
    groups = [[first]]
    types = {type(first):0}
    for item in it:
        item_type = type(item)
        if item_type in types.keys():
            groups[types[item_type]].append(item)
        else:
            types[item_type] = len(types)
            groups.append([item])

    #debuggng
    for group in groups:
        print(group)
        for it in group:
            print(type(it),)
    #

    for i in range(len(groups)):
        if type(groups[i][0].__gt__(groups[i][0])) == notImpl:
            continue
        groups[i] = sorted(groups[i])

    return itertools.chain.from_iterable(group for group in groups)

x = [0j, 'one', 2.3, 'four', -5, 3j, 0j,  -5.5, 13 , 15.3, 'aa', 'zz']
print(list(p2Sort(x)))
注意,需要一个额外的字典来保存列表中的不同类型,还需要一个类型保存变量(notImpl)。进一步注意,这里不混合float和int

输出:

================================================================================
05.04.2017 18:27:57
~/Desktop/sorter.py
--------------------------------------------------------------------------------
[0j, 3j, 0j]
<class 'complex'>
<class 'complex'>
<class 'complex'>
['one', 'four', 'aa', 'zz']
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
[2.3, -5.5, 15.3]
<class 'float'>
<class 'float'>
<class 'float'>
[-5, 13]
<class 'int'>
<class 'int'>
[0j, 3j, 0j, 'aa', 'four', 'one', 'zz', -5.5, 2.3, 15.3, -5, 13]
================================================================================
05.04.2017 18:27:57
~/Desktop/sorter.py
--------------------------------------------------------------------------------
[0j,3j,0j]
['1','4','aa','zz']
[2.3, -5.5, 15.3]
[-5, 13]
[0j,3j,0j,'aa','four','one','zz',-5.5,2.3,15.3,-5,13]

Python 3.2+的一种方法是使用。 有了它,您可以快速实现一个解决方案,该解决方案尝试比较值,然后再回头比较类型的字符串表示形式。您还可以避免在比较无序类型时引发错误,并保持顺序与原始情况相同:

from functools import cmp_to_key

def cmp(a,b):
    try:
        return (a > b) - (a < b)
    except TypeError:
        s1, s2 = type(a).__name__, type(b).__name__
        return (s1 > s2) - (s1 < s2)

这样做的缺点是总是进行三方比较,增加了时间复杂度。但是,该解决方案开销低、简洁,我认为
cmp\u to\u key()
是为这种Python 2仿真用例开发的。

我们可以通过以下方式解决此问题

  • 按类型分组
  • 通过尝试比较每种类型的单一代表性,找出可比较的类型
  • 合并可比较类型的组
  • 如果可能,对合并的组进行排序
  • 来自(已排序)合并组的产量
  • 通过使用
    repr(type(x))
    ,我们可以从类型中获得确定性和可排序的键函数。请注意,此处的“类型层次结构”由类型本身的repr决定。此方法中的一个缺陷是,如果两个类型具有相同的
    \uuuu repr\uuuu
    (类型本身,而不是实例),则会“混淆”类型。这可以通过使用一个返回元组
    (repr(type),id(type))
    的键函数来解决,但我没有在这个解决方案中实现它

    与Bas Swinkel方法相比,我的方法的优点是更干净地处理一组不可排序的元素。我们没有二次行为;相反,在sorted()期间第一次尝试排序后,函数将放弃

    在iterable中有大量不同类型的情况下,我的方法功能最差。这是一种罕见的情况,但我想它可能会出现

    def py2sort(iterable):
            by_type_repr = lambda x: repr(type(x))
            iterable = sorted(iterable, key = by_type_repr)
            types = {type_: list(group) for type_, group in groupby(iterable, by_type_repr)}
    
            def merge_compatible_types(types):
                representatives = [(type_, items[0]) for (type_, items) in types.items()]
    
                def mergable_types():
                    for i, (type_0, elem_0) in enumerate(representatives, 1):
                        for type_1, elem_1 in representatives[i:]:
                             if _comparable(elem_0, elem_1):
                                 yield type_0, type_1
    
                def merge_types(a, b):
                    try:
                        types[a].extend(types[b])
                        del types[b]
                    except KeyError:
                        pass # already merged
    
                for a, b in mergable_types():
                    merge_types(a, b)
                return types
    
            def gen_from_sorted_comparable_groups(types):
                for _, items in types.items():
                    try:
                        items = sorted(items)
                    except TypeError:
                        pass #unorderable type
                    yield from items
            types = merge_compatible_types(types)
            return list(gen_from_sorted_comparable_groups(types))
    
        def _comparable(x, y):
            try:
                x < y
            except TypeError:
                return False
            else:
                return True
    
        if __name__ == '__main__':    
            print('before py2sort:')
            test = [2, -11.6, 3, 5.0, (1, '5', 3), (object, object()), complex(2, 3), [list, tuple], Fraction(11, 2), '2', type, str, 'foo', object(), 'bar']    
            print(test)
            print('after py2sort:')
            print(py2sort(test))
    
    def py2sort(iterable):
    by_type_repr=lambda x:repr(type(x))
    iterable=已排序(iterable,key=按类型\u repr)
    types={type\:type\的列表(组),groupby中的组(iterable,by_
    
    #! /usr/bin/python3
    
    import itertools
    
    def p2Sort(x):
        notImpl = type(0j.__gt__(0j))
        it = iter(x)
        first = next(it)
        groups = [[first]]
        types = {type(first):0}
        for item in it:
            item_type = type(item)
            if item_type in types.keys():
                groups[types[item_type]].append(item)
            else:
                types[item_type] = len(types)
                groups.append([item])
    
        #debuggng
        for group in groups:
            print(group)
            for it in group:
                print(type(it),)
        #
    
        for i in range(len(groups)):
            if type(groups[i][0].__gt__(groups[i][0])) == notImpl:
                continue
            groups[i] = sorted(groups[i])
    
        return itertools.chain.from_iterable(group for group in groups)
    
    x = [0j, 'one', 2.3, 'four', -5, 3j, 0j,  -5.5, 13 , 15.3, 'aa', 'zz']
    print(list(p2Sort(x)))
    
    ================================================================================
    05.04.2017 18:27:57
    ~/Desktop/sorter.py
    --------------------------------------------------------------------------------
    [0j, 3j, 0j]
    <class 'complex'>
    <class 'complex'>
    <class 'complex'>
    ['one', 'four', 'aa', 'zz']
    <class 'str'>
    <class 'str'>
    <class 'str'>
    <class 'str'>
    [2.3, -5.5, 15.3]
    <class 'float'>
    <class 'float'>
    <class 'float'>
    [-5, 13]
    <class 'int'>
    <class 'int'>
    [0j, 3j, 0j, 'aa', 'four', 'one', 'zz', -5.5, 2.3, 15.3, -5, 13]
    
    from functools import cmp_to_key
    
    def cmp(a,b):
        try:
            return (a > b) - (a < b)
        except TypeError:
            s1, s2 = type(a).__name__, type(b).__name__
            return (s1 > s2) - (s1 < s2)
    
    sorted([0, 'one', 2.3, 'four', -5], key=cmp_to_key(cmp))
    # [-5, 0, 2.3, 'four', 'one']
    sorted([0, 123.4, 5, -6, 7.89], key=cmp_to_key(cmp))
    # [-6, 0, 5, 7.89, 123.4]
    sorted([{1:2}, {3:4}], key=cmp_to_key(cmp))
    # [{1: 2}, {3: 4}]
    sorted([{1:2}, None, {3:4}], key=cmp_to_key(cmp))
    # [None, {1: 2}, {3: 4}]
    
    def py2sort(iterable):
            by_type_repr = lambda x: repr(type(x))
            iterable = sorted(iterable, key = by_type_repr)
            types = {type_: list(group) for type_, group in groupby(iterable, by_type_repr)}
    
            def merge_compatible_types(types):
                representatives = [(type_, items[0]) for (type_, items) in types.items()]
    
                def mergable_types():
                    for i, (type_0, elem_0) in enumerate(representatives, 1):
                        for type_1, elem_1 in representatives[i:]:
                             if _comparable(elem_0, elem_1):
                                 yield type_0, type_1
    
                def merge_types(a, b):
                    try:
                        types[a].extend(types[b])
                        del types[b]
                    except KeyError:
                        pass # already merged
    
                for a, b in mergable_types():
                    merge_types(a, b)
                return types
    
            def gen_from_sorted_comparable_groups(types):
                for _, items in types.items():
                    try:
                        items = sorted(items)
                    except TypeError:
                        pass #unorderable type
                    yield from items
            types = merge_compatible_types(types)
            return list(gen_from_sorted_comparable_groups(types))
    
        def _comparable(x, y):
            try:
                x < y
            except TypeError:
                return False
            else:
                return True
    
        if __name__ == '__main__':    
            print('before py2sort:')
            test = [2, -11.6, 3, 5.0, (1, '5', 3), (object, object()), complex(2, 3), [list, tuple], Fraction(11, 2), '2', type, str, 'foo', object(), 'bar']    
            print(test)
            print('after py2sort:')
            print(py2sort(test))
    
    lst = [0, 'one', 2.3, 'four', -5]
    a=[x for x in lst if type(x) == type(1) or type(x) == type(1.1)] 
    b=[y for y in lst if type(y) == type('string')]
    a.sort()
    b.sort()
    c = a+b
    print(c)
    
    import unittest
    from sort2 import sorted2
    
    
    class TestSortNumbers(unittest.TestCase):
        """
        Verifies numbers are get sorted correctly.
        """
    
        def test_sort_empty(self):
            self.assertEqual(sorted2([]), [])
    
        def test_sort_one_element_int(self):
            self.assertEqual(sorted2([1]), [1])
    
        def test_sort_one_element_real(self):
            self.assertEqual(sorted2([1.0]), [1.0])
    
        def test_ints(self):
            self.assertEqual(sorted2([1, 2]), [1, 2])
    
        def test_ints_reverse(self):
            self.assertEqual(sorted2([2, 1]), [1, 2])
    
    
    class TestSortStrings(unittest.TestCase):
        """
        Verifies numbers are get sorted correctly.
        """
    
        def test_sort_one_element_str(self):
            self.assertEqual(sorted2(["1.0"]), ["1.0"])
    
    
    class TestSortIntString(unittest.TestCase):
        """
        Verifies numbers and strings are get sorted correctly.
        """
    
        def test_string_after_int(self):
            self.assertEqual(sorted2([1, "1"]), [1, "1"])
            self.assertEqual(sorted2([0, "1"]), [0, "1"])
            self.assertEqual(sorted2([-1, "1"]), [-1, "1"])
            self.assertEqual(sorted2(["1", 1]), [1, "1"])
            self.assertEqual(sorted2(["0", 1]), [1, "0"])
            self.assertEqual(sorted2(["-1", 1]), [1, "-1"])
    
    
    class TestSortIntDict(unittest.TestCase):
        """
        Verifies numbers and dict are get sorted correctly.
        """
    
        def test_string_after_int(self):
            self.assertEqual(sorted2([1, {1: 2}]), [1, {1: 2}])
            self.assertEqual(sorted2([0, {1: 2}]), [0, {1: 2}])
            self.assertEqual(sorted2([-1, {1: 2}]), [-1, {1: 2}])
            self.assertEqual(sorted2([{1: 2}, 1]), [1, {1: 2}])
            self.assertEqual(sorted2([{1: 2}, 1]), [1, {1: 2}])
            self.assertEqual(sorted2([{1: 2}, 1]), [1, {1: 2}])
    
    from numbers import Real
    from decimal import Decimal
    from itertools import tee, filterfalse
    
    
    def sorted2(iterable):
        """
    
        :param iterable: An iterable (array or alike)
            entity which elements should be sorted.
        :return: List with sorted elements.
        """
        def predicate(x):
            return isinstance(x, (Real, Decimal))
    
        t1, t2 = tee(iterable)
        numbers = filter(predicate, t1)
        non_numbers = filterfalse(predicate, t2)
        sorted_numbers = sorted(numbers)
        sorted_non_numbers = sorted(non_numbers, key=str)
        return sorted_numbers + sorted_non_numbers
    
    >>> from sort2 import sorted2
    >>> sorted2([1,2,3, "aaa", {3:5}, [1,2,34], {-8:15}])
    [1, 2, 3, [1, 2, 34], 'aaa', {-8: 15}, {3: 5}]
    
    def default_3way_compare(v, w):  # Yes, this is how Python 2 sorted things :)
        tv, tw = type(v), type(w)
        if tv is tw:
            return -1 if id(v) < id(w) else (1 if id(v) > id(w) else 0)
        if v is None:
            return -1
        if w is None:
            return 1
        if isinstance(v, (int, float)):
            vname = ''
        else:
            vname = type(v).__name__
        if isinstance(w, (int, float)):
            wname = ''
        else:
            wname = type(w).__name__
        if vname < wname:
            return -1
        if vname > wname:
            return 1
        return -1 if id(type(v)) < id(type(w)) else 1
    
    def py2key(func=None):  # based on cmp_to_key
        class K(object):
            __slots__ = ['obj']
            __hash__ = None
    
            def __init__(self, obj):
                self.obj = func(obj) if func else obj
    
            def __lt__(self, other):
                try:
                    return self.obj < other.obj
                except TypeError:
                    return default_3way_compare(self.obj, other.obj) < 0
    
            def __gt__(self, other):
                try:
                    return self.obj > other.obj
                except TypeError:
                    return default_3way_compare(self.obj, other.obj) > 0
    
            def __eq__(self, other):
                try:
                    return self.obj == other.obj
                except TypeError:
                    return default_3way_compare(self.obj, other.obj) == 0
    
            def __le__(self, other):
                try:
                    return self.obj <= other.obj
                except TypeError:
                    return default_3way_compare(self.obj, other.obj) <= 0
    
            def __ge__(self, other):
                try:
                    return self.obj >= other.obj
                except TypeError:
                    return default_3way_compare(self.obj, other.obj) >= 0
        return K
    
    from numbers import Number
    
    
    # decorator for type to function mapping special cases
    def per_type_cmp(type_):
        try:
            mapping = per_type_cmp.mapping
        except AttributeError:
            mapping = per_type_cmp.mapping = {}
        def decorator(cmpfunc):
            mapping[type_] = cmpfunc
            return cmpfunc
        return decorator
    
    
    class python2_sort_key(object):
        _unhandled_types = {complex}
    
        def __init__(self, ob):
           self._ob = ob
    
        def __lt__(self, other):
            _unhandled_types = self._unhandled_types
            self, other = self._ob, other._ob  # we don't care about the wrapper
    
            # default_3way_compare is used only if direct comparison failed
            try:
                return self < other
            except TypeError:
                pass
    
            # hooks to implement special casing for types, dict in Py2 has
            # a dedicated __cmp__ method that is gone in Py3 for example.
            for type_, special_cmp in per_type_cmp.mapping.items():
                if isinstance(self, type_) and isinstance(other, type_):
                    return special_cmp(self, other)
    
            # explicitly raise again for types that won't sort in Python 2 either
            if type(self) in _unhandled_types:
                raise TypeError('no ordering relation is defined for {}'.format(
                    type(self).__name__))
            if type(other) in _unhandled_types:
                raise TypeError('no ordering relation is defined for {}'.format(
                    type(other).__name__))
    
            # default_3way_compare from Python 2 as Python code
            # same type but no ordering defined, go by id
            if type(self) is type(other):
                return id(self) < id(other)
    
            # None always comes first
            if self is None:
                return True
            if other is None:
                return False
    
            # Sort by typename, but numbers are sorted before other types
            self_tname = '' if isinstance(self, Number) else type(self).__name__
            other_tname = '' if isinstance(other, Number) else type(other).__name__
    
            if self_tname != other_tname:
                return self_tname < other_tname
    
            # same typename, or both numbers, but different type objects, order
            # by the id of the type object
            return id(type(self)) < id(type(other))
    
    
    @per_type_cmp(dict)
    def dict_cmp(a, b, _s=object()):
        if len(a) != len(b):
            return len(a) < len(b)
        adiff = min((k for k in a if a[k] != b.get(k, _s)), key=python2_sort_key, default=_s)
        if adiff is _s:
            # All keys in a have a matching value in b, so the dicts are equal
            return False
        bdiff = min((k for k in b if b[k] != a.get(k, _s)), key=python2_sort_key)
        if adiff != bdiff:
            return python2_sort_key(adiff) < python2_sort_key(bdiff)
        return python2_sort_key(a[adiff]) < python2_sort_key(b[bdiff])
    
    @per_type_cmp(list)
    def list_cmp(a, b):
        for a_item, b_item in zip(a, b):
            if a_item == b_item:
                continue
            return python2_sort_key(a_item) < python2_sort_key(b_item)
        return len(a) < len(b)