Сортировка списка по атрибуту, который может быть None

Я пытаюсь отсортировать список объектов с помощью

my_list.sort(key=operator.attrgetter(attr_name))

но если какой-либо из элементов списка имеет attr = None вместо attr = 'whatever',

то я получаю a TypeError: unorderable types: NoneType() < str()

В Py2 это не проблема. Как мне обрабатывать это в Py3?

Ответ 1

Операторы сравнения порядка более строгие относительно типов в Python 3, как описано здесь:

Операторы сравнения порядка (<, < =, > =, > ) создают TypeError исключение, когда операнды не имеют значимого естественного упорядочения.

Python 2 сортирует None перед любой строкой (даже пустой строкой):

>>> None < None
False

>>> None < "abc"
True

>>> None < ""
True

В Python 3 любые попытки упорядочения экземпляров NoneType приводят к исключению:

>>> None < "abc"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: NoneType() < str()

Самое быстрое исправление, о котором я могу думать, это явно сопоставить экземпляры None с чем-то упорядоченным, например "":

my_list_sortable = [(x or "") for x in my_list]

Если вы хотите сортировать свои данные, сохраняя их неповрежденными, просто дайте sort индивидуальный метод key:

def nonesorter(a):
    if not a:
        return ""
    return a

my_list.sort(key=nonesorter)

Ответ 2

Для общего решения вы можете определить объект, который сравнивается меньше любого другого объекта:

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()

Затем используйте ключ сортировки, который заменяет Min для любых значений None в списке

mylist.sort(key=lambda x: Min if x is None else x)

Ответ 3

Так как есть другие вещи помимо None, которые не сопоставимы с строкой (ints и lists, for starters), вот более надежное решение общей проблемы:

my_list.sort(key=lambda x: x if isinstance(x, str) else "")

Это приведет к тому, что строки и любой тип, полученные из str, будут сравниваться как сами по себе, а также все остальное с пустой строкой. Или замените другой стандартный по умолчанию ключ, если хотите, например. "ZZZZ" или chr(sys.maxunicode), чтобы сделать такие элементы сортированными в конце.