'Find an analogue for the Python 2.7 sorting behavior in Python 3.x [duplicate]

In Python 2.7 there was an ability to use the sorted function with a list that contains different types, e.g. string, tuple, integer, like this:

some_list = ['a', 'b', 1, ('c', 'd'), [2, 3, 4]]

>>> sorted(some_list)

[1, [2, 3, 4], 'a', 'b', ('c', 'd')]

However, if we try to do something like this in Python 3, we will get an exception:

some_list = ['a', 'b', 1, ('c', 'd'), [2, 3, 4]]

>>> sorted(some_list)

TypeError: '<' not supported between instances of 'int' and 'str'

What's the easiest way to mimic the behavior of the sorted function from python2.7? I understand that I can just write my own method for this purposes, but maybe a similar functionality was implemented in some library or moved to another specific built-in function?



Solution 1:[1]

You need to supply an explicit key to sort or sorted, and the key should explain how to compare elements of different types.

A quick solution is to replace element x with a tuple (type(x).__name__, x) so that elements are grouped by type first, and only compared if they have the same type:

some_list = ['a', [4, 5, 6], 3, 2.0, 'b', 1, 1.5, ('c', 'd'), [2, 3, 4]]

some_list.sort(key=lambda x: (type(x).__name__, x))

print(some_list)
# [1.5, 2.0, 1, 3, [2, 3, 4], [4, 5, 6], 'a', 'b', ('c', 'd')]

Note how the integers were separated from the floats. If you don't want that, you need a more complex key. I give an example at the bottom of this post.

You could try other keys, which will result in different orders:

  • key=str or key=repr: Brutally convert everything to string before comparison. This has the downside that numbers are compared lexicographically, so that 1 < 10 < 2.
  • key=lambda x: tuple(more_itertools.collapse(x)) this flattens everything into tuples, so that a single number is equivalent to a list of one number, and a list of lists of lists is equivalent to a simple list. But it will still crash if trying to compare numbers with strings.
  • Some hybrid method of the two solutions above. You can declare a complex key like any function, using the def keyword.
  • Try comparing the elements with their usual comparison, then if it raises an exception, convert the elements into strings and compare again:
from functools import cmp_to_key

def cmp(a,b):
    try:
        return (a > b) - (a < b)
    except TypeError:
        a, b = str(a), str(b)
        return (a > b) - (a < b)

k = cmp_to_key(cmp)

some_list = ['a', [4, 5, 6], 3, 2.0, 'b', 1, 1.5, ('c', 'd'), [2, 3, 4]]

some_list.sort(key=k)

print(some_list)
# [('c', 'd'), 1, 1.5, 2.0, 3, [2, 3, 4], [4, 5, 6], 'a', 'b']

Solution 2:[2]

You ought to be able to get what you're after with an appropriate key function. E.g.:

>>> some_list = ['a', 'b', 1, ('c', 'd'), [2, 3, 4]]
>>> sorted(some_list, key=lambda i: str(i[0] if hasattr(i, "__getitem__") else i))
[1, [2, 3, 4], 'a', 'b', ('c', 'd')]

The details of the key will depend on exactly how you want to handle comparisons between items of differing types -- the example above assumes you want to use string-based comparison, but there might be other methods.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Samwise