I'm trying to replicate (and if possible improve on) Python 2.x's sorting behaviour in 3.x, so that mutually orderable types like int
, float
etc. are sorted as expected, and mutually unorderable types are grouped within the output.
Here's an example of what I'm talking about:
>>> sorted([0, 'one', 2.3, 'four', -5]) # Python 2.x
[-5, 0, 2.3, 'four', 'one']
>>> sorted([0, 'one', 2.3, 'four', -5]) # Python 3.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() < int()
My previous attempt at this, using a class for the key parameter to sorted()
(see
Why does this key class for sorting heterogeneous sequences behave oddly?) is fundamentally broken, because its approach of
- Trying to compare values, and
- If that fails, falling back to comparing the string representation of their types
can lead to intransitive ordering, as explained by BrenBarn's excellent answer.
A naïve approach, which I initially rejected without even trying to code it, would be to use a key function that returns a (type, value)
tuple:
def motley(value):
return repr(type(value)), value
However, this doesn't do what I want. In the first place, it breaks the natural ordering of mutually orderable types:
>>> 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]
Secondly, it raises an exception when the input contains two objects of the same intrinsically unorderable type:
>>> sorted([{1:2}, {3:4}], key=motley)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()
... which admittedly is the standard behaviour in both Python 2.x and 3.x – but ideally I'd like such types to be grouped together (I don't especially care about their ordering, but it would seem in keeping with Python's guarantee of stable sorting that they retain their original order).
I can work around the first of these problems for numeric types by special-casing them:
from numbers import Real
from decimal import Decimal
def motley(value):
numeric = Real, Decimal
if isinstance(value, numeric):
typeinfo = numeric
else:
typeinfo = type(value)
return repr(typeinfo), value
... which works as far as it goes:
>>> sorted([0, 'one', 2.3, 'four', -5], key=motley)
[-5, 0, 2.3, 'four', 'one']
... but doesn't account for the fact that there may be other distinct (possibly user-defined) types which are mutually orderable, and of course still fails with intrinsically unorderable types:
>>> sorted([{1:2}, {3:4}], key=motley)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()
Is there another approach which solves both the problem of arbitrary, distinct-but-mutually-orderable types and that of intrinsically unorderable types?
b'z' < (1, 2) < u'a' < b'z'
(because'str' < 'tuple' < 'unicode'
, butstr
andunicode
are value-comparable in Python 2. I'm curious, though, what your actual use case is for wanting to do this for arbitrary types (instead of just "manually" grouping, say, numeric types). – Compoundpprint
goes about displaying dictionaries with the keys in order, and being more in a mood to think it through than go look at the source. Actually, I should probably go do that now :-) – Watereddownmotley
class from my previous question! The use of a(str(type(obj)), id(obj))
return value is presumably to mitigate the problems you raised with that approach, although I don't think it can do so completely. – Watereddown