Шаг 14.
Python: сборник рецептов.
Структуры данных и алгоритмы. Сортировка списка словарей по общему ключу

    На этом шаге мы рассмотрим использование функции operator.itemgetter().

Задача

    У вас есть список словарей, и вы хотите отсортировать записи согласно одному или более значениям.

Решение

    Сортировка структур этого типа легко выполняется с помощью функции itemgetter() из модуля operator. Предположим, вы выполнили запрос к таблице базы данных, чтобы получить список зарегистрированных пользователей вашего сайта, и получили в ответ вот такую структуру данных:

>>> rows = [
	{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
	{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
	{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
	{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
	]

    Можно достаточно легко вывести эти строки с упорядочением по любому из полей, общих для всех словарей. Например:

>>> from operator import itemgetter
>>> rows_by_fname = sorted(rows, key=itemgetter('fname'))
>>> rows_by_uid = sorted(rows, key=itemgetter('uid'))
>>> print(rows_by_fname)
>>> print(rows_by_uid)
>>> 

    Вышеприведенный код выведет следующее:

[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, 
 {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, 
 {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, 
 {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]

[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, 
 {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, 
 {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, 
 {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]

    Функция itemgetter() также может принимать несколько ключей. Пример кода:

>>> rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
>>> print(rows_by_lfname)
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, 
 {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, 
 {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, 
 {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
>>>


Обсуждение

    В этом примере строки передаются встроенной функции sorted(), которая принимает именованный аргумент key. Этот аргумент должен быть вызываемым объектом, который принимает один элемент из rows и возвращает значение, которое будет использовано в качестве основы для сортировки. Функция itemgetter() создает такой вызываемый объект.


Вызываемый объект - это объект, который имеет метод __call__().

    Функция operator.itemgetter() принимает в качестве аргументов индексы, которые используются для извлечения нужных значений из записей в rows. Это может быть ключ словаря, номер элемента в списке или любое другое значение, которое может быть передано методу getitem(). Если вы передадите несколько индексов функции itemgetter(), вызываемый объект, который она создаст, вернет кортеж со всеми элементами, и функция sorted() упорядочит выводимые элементы в соответствии с отсортированным порядком кортежей. Это может быть полезно, если вы хотите провести сортировку сразу по нескольким полям (в примере это имя и фамилия).

    Функциональность itemgetter() иногда может быть заменена lambda-выражением. Например:

rows_by_fname = sorted(rows, key=lambda r: r['fname']) 
rows_by_lfname = sorted(rows, key=lambda r: (r['lname'], r['fname']))

    Это решение в большинстве случаев работает отлично. Однако решение с использованием itemgetter() обычно выполняется быстрее. Так что обратите на него внимание, если производительность в приоритете.

    Последнее по порядку, но не по значению: не забудьте, что описанная в этом рецепте техника может быть применена к таким функциям, как min() и max(). Например:

>>> min(rows, key=itemgetter('uid'))
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
>>> max(rows, key=itemgetter('uid'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
>>> 

    На следующем шаге мы рассмотрим сортировку объектов, не поддерживающих сравнение.




Предыдущий шаг Содержание Следующий шаг