Sorting list by an attribute that can be None

前端 未结 4 613
栀梦
栀梦 2020-11-30 09:53

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

相关标签:
4条回答
  • 2020-11-30 10:06

    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)
    
    0 讨论(0)
  • 2020-11-30 10:10

    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]
    
    0 讨论(0)
  • 2020-11-30 10:22

    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.

    0 讨论(0)
  • 2020-11-30 10:24

    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)
    
    0 讨论(0)
提交回复
热议问题