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']