Wie sortiere ich eine Liste von Objekten basierend auf einem Attribut der Objekte?


804

Ich habe eine Liste von Python-Objekten, die ich nach einem Attribut der Objekte selbst sortieren möchte. Die Liste sieht aus wie:

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

Jedes Objekt hat eine Anzahl:

>>> ut[1].count
1L

Ich muss die Liste nach absteigender Anzahl sortieren.

Ich habe verschiedene Methoden dafür gesehen, aber ich suche nach Best Practices in Python.



1
Sortieranleitung für diejenigen, die weitere Informationen zum Sortieren in Python suchen.
Jeyekomon

1
Neben operator.attrgetter ('attribute_name') können Sie auch Funktoren als Schlüssel wie object_list.sort (key = my_sorting_functor ('my_key')) verwenden, wobei die Implementierung absichtlich weggelassen wird.
Vijay Shanker

Antworten:


1313
# 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)

Mehr zum Sortieren nach Schlüsseln .


1
Kein Problem. Übrigens, wenn Muhuk Recht hat und es sich um eine Liste von Django-Objekten handelt, sollten Sie über seine Lösung nachdenken. Für den allgemeinen Fall des Sortierens von Objekten ist meine Lösung jedoch wahrscheinlich die beste Vorgehensweise.
Triptychon

43
Bei großen Listen erzielen Sie eine bessere Leistung, wenn Sie operator.attrgetter ('count') als Schlüssel verwenden. Dies ist nur eine optimierte (niedrigere) Form der Lambda-Funktion in dieser Antwort.
David Eyk

4
Danke für die tolle Antwort. Wenn es sich um eine Liste von Wörterbüchern handelt und 'count' einer der Schlüssel ist, muss es wie folgt geändert werden: ut.sort (key = lambda x: x ['count'], reverse = True)
dganesh2002

Ich nehme an, es verdient das folgende Update: Wenn nach mehreren Feldern sortiert werden muss, kann dies durch aufeinanderfolgende Aufrufe von sort () erreicht werden, da Python einen stabilen Sortieralgorithmus verwendet.
zzz777

86

Ein Weg, der am schnellsten sein kann, insbesondere wenn Ihre Liste viele Datensätze enthält, ist die Verwendung operator.attrgetter("count"). Dies könnte jedoch auf einer Voroperatorversion von Python ausgeführt werden, daher wäre es schön, einen Fallback-Mechanismus zu haben. Dann möchten Sie vielleicht Folgendes tun:

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
Hier würde ich den Variablennamen "keyfun" anstelle von "cmpfun" verwenden, um Verwirrung zu vermeiden. Die sort () -Methode akzeptiert auch eine Vergleichsfunktion über das Argument cmp =.
Akaihola

Dies scheint nicht zu funktionieren, wenn das Objekt dynamisch Attribute hinzugefügt hat (wenn Sie dies self.__dict__ = {'some':'dict'}nach der __init__Methode getan haben ). Ich weiß allerdings nicht, warum es anders sein sollte.
Tutuca

@tutuca: Ich habe die Instanz nie ersetzt __dict__. Beachten Sie, dass "ein Objekt mit dynamisch hinzugefügten Attributen" und "Festlegen des __dict__Attributs eines Objekts " fast orthogonale Konzepte sind. Ich sage das, weil Ihr Kommentar zu implizieren scheint, dass das Festlegen des __dict__Attributs eine Voraussetzung für das dynamische Hinzufügen von Attributen ist.
Zot

@tzot: Ich schaue genau hin: github.com/stochastic-technologies/goatfish/blob/master/… und benutze diesen Iterator hier: github.com/TallerTechnologies/dishey/blob/master/app.py#L28 erhöht Attributfehler. Vielleicht wegen Python3, aber immer noch ...
Tutuca

1
@tzot: Wenn ich die Verwendung von verstehe operator.attrgetter, könnte ich eine Funktion mit einem beliebigen Eigenschaftsnamen angeben und eine sortierte Sammlung zurückgeben.
IAbstract

64

Leser sollten beachten, dass der Schlüssel = Methode:

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

ist um ein Vielfaches schneller als das Hinzufügen umfangreicher Vergleichsoperatoren zu den Objekten. Ich war überrascht, dies zu lesen (Seite 485 von "Python in a Nutshell"). Sie können dies bestätigen, indem Sie Tests mit diesem kleinen Programm ausführen:

#!/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

Meine sehr minimalen Tests zeigen, dass die erste Sorte mehr als zehnmal langsamer ist, aber das Buch sagt, dass sie im Allgemeinen nur fünfmal langsamer ist. Der Grund dafür liegt in dem hochoptimierten Sortieralgorithmus, der in Python ( Timsort ) verwendet wird.

Trotzdem ist es sehr seltsam, dass .sort (Lambda) schneller ist als einfaches altes .sort (). Ich hoffe, dass sie das beheben.


1
Das Definieren __cmp__ist gleichbedeutend mit dem Aufrufen .sort(cmp=lambda), nicht .sort(key=lambda), also ist es überhaupt nicht seltsam.
Zot

@tzot ist genau richtig. Die erste Sortierung muss Objekte immer wieder miteinander vergleichen. Die zweite Sortierung greift nur einmal auf jedes Objekt zu, um seinen Zählwert zu extrahieren, und führt dann eine einfache numerische Sortierung durch, die stark optimiert ist. Ein fairerer Vergleich wäre longList2.sort(cmp = cmp). Ich habe das ausprobiert und es lief fast genauso wie .sort(). (Beachten Sie auch, dass der Sortierparameter "cmp" in Python 3 entfernt wurde.)
Bryan Roach

43

Objektorientierter Ansatz

Es wird empfohlen, die Objektsortierlogik gegebenenfalls zu einer Eigenschaft der Klasse zu machen, anstatt sie in jede Instanz einzubeziehen, für die die Reihenfolge erforderlich ist.

Dies stellt die Konsistenz sicher und macht Boilerplate-Code überflüssig.

Zumindest sollten Sie angeben __eq__und __lt__Operationen ausführen, damit dies funktioniert. Dann einfach benutzen 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
Das habe ich gesucht! Können Sie uns auf eine Dokumentation verweisen, in der erläutert wird, warum __eq__und __lt__welche Mindestanforderungen für die Implementierung gelten?
FriendFX

1
@FriendFX, ich glaube, es ist impliziert dies :•The sort routines are guaranteed to use __lt__() when making comparisons between two objects...
jpp

2
@FriendFX: Vergleich und Sortierung finden Sie unter portingguide.readthedocs.io/en/latest/comparisons.html
Cornel Masson,

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

16

Es ähnelt einer Liste von Django ORM-Modellinstanzen.

Warum sortieren Sie sie nicht bei Abfragen wie folgt:

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

Es ist so, aber mit Django-Tagging, also habe ich ein eingebautes Tag verwendet, um einen Tag-Satz nach Verwendung für einen bestimmten Abfragesatz zu erfassen, wie zum Beispiel: Tag.objects.usage_for_queryset (QuerySet, count = True)
Nick Sergeant

11

Fügen Sie der Objektklasse umfangreiche Vergleichsoperatoren hinzu, und verwenden Sie dann die sort () -Methode der Liste.
Siehe reichhaltigen Vergleich in Python .


Update : Obwohl diese Methode funktionieren würde, denke ich, dass die Lösung von Triptychon besser für Ihren Fall geeignet ist, weil sie viel einfacher ist.


3

Wenn das Attribut, nach dem Sie sortieren möchten, eine Eigenschaft ist , können Sie das Importieren operator.attrgetterund Verwenden der Eigenschaft vermeidenfget stattdessen Methode verwenden.

Für eine Klasse Circlemit einer Eigenschaft radiuskönnten wir beispielsweise eine Liste circlesnach Radien wie folgt sortieren :

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

Dies ist nicht die bekannteste Funktion, erspart mir aber oft eine Zeile beim Import.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.