I\'m trying to sort a list of objects using
my_list.sort(key=operator.attrgetter(attr_name))
but if any of the list items has attr = None
The ordering comparison operators are stricter about types in Python 3, as described here:
The ordering comparison operators (<, <=, >=, >) raise a TypeError exception when the operands don’t have a meaningful natural ordering.
Python 2 sorts None
before any string (even empty string):
>>> None < None
False
>>> None < "abc"
True
>>> None < ""
True
In Python 3 any attempts at ordering NoneType
instances result in an exception:
>>> None < "abc"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: NoneType() < str()
The quickest fix I can think of is to explicitly map None
instances into something orderable like ""
:
my_list_sortable = [(x or "") for x in my_list]
If you want to sort your data while keeping it intact, just give sort
a customized key
method:
def nonesorter(a):
if not a:
return ""
return a
my_list.sort(key=nonesorter)
The solutions proposed here work, but this could be shortened further:
mylist.sort(key=lambda x: x or 0)
In essence, we can treat None as if it had value 0.
E.g.:
>>> mylist = [3, 1, None, None, 2, 0]
>>> mylist.sort(key=lambda x: x or 0)
>>> mylist
[None, None, 0, 1, 2, 3]
Since there are other things besides None
that are not comparable to a string (ints and lists, for starters), here is a more robust solution to the general problem:
my_list.sort(key=lambda x: x if isinstance(x, str) else "")
This will let strings and any type derived from str
to compare as themselves, and bin everything else with the empty string. Or substitute a different default default key if you prefer, e.g. "ZZZZ"
or chr(sys.maxunicode)
to make such elements sort at the end.
For a general solution, you can define an object that compares less than any other object:
from functools import total_ordering
@total_ordering
class MinType(object):
def __le__(self, other):
return True
def __eq__(self, other):
return (self is other)
Min = MinType()
Then use a sort key that substitutes Min
for any None
values in the list
mylist.sort(key=lambda x: Min if x is None else x)