Як сортувати список об’єктів на основі атрибутів об’єктів?


804

У мене є список об’єктів Python, які я хотів би сортувати за атрибутом самих об’єктів. Список виглядає так:

>>> ut
[<Tag: 128>, <Tag: 2008>, <Tag: <>, <Tag: actionscript>, <Tag: addresses>,
 <Tag: aes>, <Tag: ajax> ...]

Кожен об’єкт має кількість:

>>> ut[1].count
1L

Мені потрібно сортувати список за кількістю підрахунків, що спадають.

Я бачив кілька методів цього, але шукаю кращі практики в Python.



1
Сортування ЗАРАЗ для тих, хто шукає додаткову інформацію про сортування в Python.
Jeyekomon

1
окрім operator.attrgetter ('attribute_name'), ви можете також використовувати функтори як ключ, наприклад object_list.sort (key = my_sorting_functor ('my_key')), не замислюючи реалізацію навмисно.
vijay

Відповіді:


1312
# To sort the list in place...
ut.sort(key=lambda x: x.count, reverse=True)

# To return a new list, use the sorted() built-in function...
newlist = sorted(ut, key=lambda x: x.count, reverse=True)

Детальніше про сортування за клавішами .


1
Нема проблем. btw, якщо мухук правий і це список об’єктів Джанго, ви повинні розглянути його рішення. Однак для загального випадку сортування об'єктів моє рішення, мабуть, найкраща практика.
Триптих

43
У великих списках ви отримаєте кращу ефективність, використовуючи operator.attrgetter ('count') як свій ключ. Це лише оптимізована (нижчий рівень) форма лямбда-функції у цій відповіді.
Девід Ейк

4
Дякую за чудову відповідь. У випадку, якщо це список словників і 'count' є одним із його ключових, то його потрібно змінити, як нижче: ut.sort (key = lambda x: x ['count'], reverse = True)
dganesh2002

Я вважаю, що це заслуговує на наступне оновлення: якщо є необхідність сортування за кількома полями, це може бути досягнуто послідовними викликами сортування (), оскільки python використовує стабільний алгоритм сортування.
zzz777

86

Швидкий спосіб, особливо якщо у вашому списку багато записів, є використання operator.attrgetter("count"). Однак це може працювати на дооператорській версії Python, тому непогано було б створити резервний механізм. Ви можете зробити наступне:

try: import operator
except ImportError: keyfun= lambda x: x.count # use a lambda if no operator module
else: keyfun= operator.attrgetter("count") # use operator since it's faster than lambda

ut.sort(key=keyfun, reverse=True) # sort in-place

7
Тут я б використав назву змінної "keyfun" замість "cmpfun", щоб уникнути плутанини. Метод sort () також приймає функцію порівняння через аргумент cmp =.
akaihola

Схоже, це не працює, якщо об’єкт динамічно додає атрибути (якщо ви зробили це self.__dict__ = {'some':'dict'}після __init__методу). Я не знаю, чому це може бути інакше.
tutuca

@tutuca: Я ніколи не замінював примірник __dict__. Зауважте, що "об'єкт, що має динамічно додані атрибути" та "встановлення __dict__атрибута об'єкта ", є майже ортогональними поняттями. Я кажу, що тому, що ваш коментар начебто означає, що встановлення __dict__атрибута є вимогою до динамічного додавання атрибутів.
цот

@tzot: Я дивлюся прямо на це: github.com/stochastic-technologies/goatfish/blob/master/… та використовую тут ітератор: github.com/TallerTechnologies/dishey/blob/master/app.py#L28 підвищує помилка атрибута. Може, через python3, але все ж ...
tutuca

1
@tzot: якщо я розумію використання operator.attrgetter, я можу поставити функцію з будь-яким ім'ям властивості та повернути відсортовану колекцію.
IАнотація

64

Читачі повинні помітити, що ключ = метод:

ut.sort(key=lambda x: x.count, reverse=True)

у багато разів швидше, ніж додавання об'єктів багатих операторів порівняння. Я був здивований, прочитавши це (стор. 485 «Пітона в горішці»). Ви можете підтвердити це, запустивши тести на цій маленькій програмі:

#!/usr/bin/env python
import random

class C:
    def __init__(self,count):
        self.count = count

    def __cmp__(self,other):
        return cmp(self.count,other.count)

longList = [C(random.random()) for i in xrange(1000000)] #about 6.1 secs
longList2 = longList[:]

longList.sort() #about 52 - 6.1 = 46 secs
longList2.sort(key = lambda c: c.count) #about 9 - 6.1 = 3 secs

Мої, дуже мінімальні тести, показують, що перший сорт більш ніж у 10 разів повільніше, але в книзі йдеться про те, що загалом це лише приблизно в 5 разів. Причина, по якій вони кажуть, пов'язана з високооптимізованим алгоритмом сортування, який використовується в python ( timsort ).

І все-таки дуже дивно, що .sort (лямбда) швидше звичайного старого .sort (). Я сподіваюся, що вони це виправлять.


1
Визначення __cmp__еквівалентно дзвінку .sort(cmp=lambda), ні .sort(key=lambda), тому воно зовсім не дивно.
tzot

@tzot - це абсолютно правильно. Перший сорт повинен порівнювати об'єкти один проти одного знову і знову. Другий сорт звертається до кожного об'єкта лише один раз, щоб отримати його значення рахунку, а потім він виконує простий числовий сортування, який є оптимізованим. Більш справедливим було б порівняння longList2.sort(cmp = cmp). Я спробував це, і це було майже так само, як і .sort(). (Також зауважте, що параметр сортування "cmp" видалено в Python 3.)
Bryan Roach

43

Об'єктно-орієнтований підхід

Доброю практикою є створення логіки сортування об'єктів, якщо це застосовно, властивістю класу, а не включеним у кожному випадку, впорядкування потрібно.

Це забезпечує узгодженість та позбавляє від необхідності кодового коду.

Як мінімум, слід вказати __eq__і __lt__операції, щоб це працювало. Тоді просто використовуйте sorted(list_of_objects).

class Card(object):

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __eq__(self, other):
        return self.rank == other.rank and self.suit == other.suit

    def __lt__(self, other):
        return self.rank < other.rank

hand = [Card(10, 'H'), Card(2, 'h'), Card(12, 'h'), Card(13, 'h'), Card(14, 'h')]
hand_order = [c.rank for c in hand]  # [10, 2, 12, 13, 14]

hand_sorted = sorted(hand)
hand_sorted_order = [c.rank for c in hand_sorted]  # [2, 10, 12, 13, 14]

1
Ось що я шукав! Чи не могли б ви вказати нам деякі документи , які конкретизують чому __eq__і __lt__вимога до мінімальної реалізації?
FriendFX

1
@FriendFX, я вважаю, що це мається на увазі під цим :•The sort routines are guaranteed to use __lt__() when making comparisons between two objects...
jpp

2
@FriendFX: Див. Portingguide.readthedocs.io/en/latest/comparisons.html для порівняння та сортування
Cornel Masson

37
from operator import attrgetter
ut.sort(key = attrgetter('count'), reverse = True)

16

Це схоже на список екземплярів моделі Django ORM.

Чому б не сортувати їх за таким запитом:

ut = Tag.objects.order_by('-count')

Так, але використовуючи django-теги, тому я використовував вбудований для захоплення набору тегів за допомогою певного набору запитів, наприклад: Tag.objects.usage_for_queryset (QuerySet, counts = True)
Нік сержант

11

Додайте розширені оператори порівняння до класу об’єктів, а потім використовуйте метод sort () списку.
Дивіться багате порівняння в python .


Оновлення : Хоча цей метод спрацював би, я думаю, що рішення від триптиху краще підходить для вашого випадку, оскільки спосіб простіший.


3

Якщо атрибут, який ви хочете сортувати, - це властивість , то ви можете уникнути імпорту operator.attrgetterта використовувати fgetнатомість метод властивості .

Наприклад, для класу Circleз властивістю radiusми можемо сортувати список circlesза радіусами наступним чином:

result = sorted(circles, key=Circle.radius.fget)

Це не найвідоміша функція, але часто економить мені рядок із імпортом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.