Come ordinare un elenco di oggetti basato su un attributo degli oggetti?


804

Ho un elenco di oggetti Python che vorrei ordinare in base a un attributo degli oggetti stessi. L'elenco è simile a:

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

Ogni oggetto ha un conteggio:

>>> ut[1].count
1L

Devo ordinare l'elenco in base al numero di conteggi decrescenti.

Ho visto diversi metodi per questo, ma sto cercando le migliori pratiche in Python.



1
Ordinamento COME PER coloro che sono alla ricerca di maggiori informazioni sull'ordinamento in Python.
Jeyekomon

1
oltre a operator.attrgetter ('nome_attributo') puoi anche usare funzioni come chiave come object_list.sort (key = my_sorting_functor ('my_key')), lasciando intenzionalmente fuori l'implementazione.
vijay shanker,

Risposte:


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

Maggiori informazioni sull'ordinamento per chiavi .


1
Nessun problema. a proposito, se muhuk ha ragione ed è un elenco di oggetti Django, dovresti considerare la sua soluzione. Tuttavia, nel caso generale dell'ordinamento di oggetti, la mia soluzione è probabilmente la migliore pratica.
Trittico

43
Su elenchi di grandi dimensioni otterrai prestazioni migliori utilizzando operator.attrgetter ('count') come chiave. Questa è solo una forma ottimizzata (di livello inferiore) della funzione lambda in questa risposta.
David Eyk,

4
Grazie per la magnifica risposta. Nel caso in cui sia un elenco di dizionari e 'count' è una delle sue chiavi, allora deve essere cambiato come sotto: ut.sort (key = lambda x: x ['count'], reverse = True)
dganesh2002

Suppongo che meriti il ​​seguente aggiornamento: se è necessario ordinare per più campi, potrebbe essere ottenuto da chiamate consecutive a sort (), perché Python utilizza un algoritmo di ordinamento stabile.
zzz777,

86

Un modo che può essere più veloce, specialmente se la tua lista ha molti record, è usare operator.attrgetter("count"). Tuttavia, questo potrebbe essere eseguito su una versione pre-operatore di Python, quindi sarebbe bello avere un meccanismo di fallback. Potresti voler fare quanto segue, quindi:

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
Qui vorrei usare il nome della variabile "keyfun" invece di "cmpfun" per evitare confusione. Il metodo sort () accetta anche una funzione di confronto tramite l'argomento cmp =.
Akaihola,

Questo non sembra funzionare se l'oggetto ha aggiunto dinamicamente attributi (se lo hai fatto self.__dict__ = {'some':'dict'}dopo il __init__metodo). Non so perché potrebbe essere diverso, però.
tutuca,

@tutuca: non ho mai sostituito l'istanza __dict__. Si noti che "un oggetto con attributi aggiunti dinamicamente" e "l'impostazione __dict__dell'attributo di un oggetto " sono concetti quasi ortogonali. Lo sto dicendo perché il tuo commento sembra implicare che l'impostazione __dict__dell'attributo sia un requisito per l'aggiunta dinamica di attributi.
Tzot

@tzot: sto guardando bene questo: github.com/stochastic-technologies/goatfish/blob/master/… e usando quell'iteratore qui: github.com/TallerTechnologies/dishey/blob/master/app.py#L28 rilancia errore di attributo. Forse a causa di python3, ma comunque ...
tutuca

1
@tzot: se capisco l'uso di operator.attrgetter, potrei fornire una funzione con qualsiasi nome di proprietà e restituire una raccolta ordinata.
Estratto del

64

I lettori dovrebbero notare che il metodo key =:

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

è molte volte più veloce dell'aggiunta di ricchi operatori di confronto agli oggetti. Sono stato sorpreso di leggere questo (pagina 485 di "Python in a Nutshell"). Puoi confermarlo eseguendo dei test su questo piccolo programma:

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

I miei test, molto minimali, mostrano che il primo tipo è più di 10 volte più lento, ma il libro dice che è solo circa 5 volte più lento in generale. Il motivo per cui dicono è dovuto all'algoritmo di ordinamento altamente ottimizzato usato in Python ( timsort ).

Tuttavia, è molto strano che .sort (lambda) sia più veloce del semplice vecchio .sort (). Spero che lo risolvano.


1
Definire __cmp__equivale a chiamare .sort(cmp=lambda), no .sort(key=lambda), quindi non è affatto strano.
Tzot

@tzot ha esattamente ragione. Il primo ordinamento deve confrontare gli oggetti uno contro l'altro ancora e ancora. Il secondo ordinamento accede a ciascun oggetto solo una volta per estrarne il valore di conteggio, quindi esegue un ordinamento numerico semplice che è altamente ottimizzato. Un confronto più equo sarebbe longList2.sort(cmp = cmp). Ho provato questo ed ha funzionato quasi come .sort(). (Inoltre: si noti che il parametro di ordinamento "cmp" è stato rimosso in Python 3.)
Bryan Roach,

43

Approccio orientato agli oggetti

È buona norma rendere la logica di ordinamento degli oggetti, se applicabile, una proprietà della classe anziché incorporata in ogni istanza in cui è richiesto l'ordinamento.

Ciò garantisce coerenza ed elimina la necessità di codice del boilerplate.

Come minimo, è necessario specificare __eq__e __lt__operazioni per farlo funzionare. Quindi basta usare 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
Questo è quello che stavo cercando! Potresti indicarci un po 'di documentazione che elabora il perché __eq__e __lt__i requisiti minimi di implementazione?
FriendFX,

1
@FriendFX, credo sia implicito in questo :•The sort routines are guaranteed to use __lt__() when making comparisons between two objects...
jpp

2
@FriendFX: vedi portingguide.readthedocs.io/en/latest/comparisons.html per il confronto e l'ordinamento
Cornel Masson,

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

16

Assomiglia molto a un elenco di istanze del modello ORM di Django.

Perché non ordinarli su query in questo modo:

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

Lo è, ma usando django-tagging, quindi stavo usando un built-in per afferrare un set di tag in base all'utilizzo per un determinato set di query, in questo modo: Tag.objects.usage_for_queryset (QuerySet, conte = True)
Nick Sergeant

11

Aggiungi operatori di confronto avanzati alla classe di oggetti, quindi usa il metodo sort () dell'elenco.
Guarda il confronto completo in Python .


Aggiornamento : sebbene questo metodo funzionerebbe, penso che la soluzione di Triptych sia più adatta al tuo caso perché molto più semplice.


3

Se l'attributo che si desidera ordinare è una proprietà , è possibile evitare l'importazione operator.attrgettere utilizzare fgetinvece il metodo della proprietà .

Ad esempio, per una classe Circlecon una proprietà radiuspotremmo ordinare un elenco di circlesraggi come segue:

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

Questa non è la caratteristica più nota ma spesso mi salva una riga con l'importazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.