989 votes

Comment trier une liste d'objets en fonction d'un attribut de ces objets ?

J'ai une liste d'objets Python que j'aimerais trier par un attribut des objets eux-mêmes. La liste ressemble à :

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

Chaque objet a un compte :

>>> ut[1].count
1L

J'ai besoin de trier la liste par nombre de comptes de manière descendante.

J'ai vu plusieurs méthodes pour cela, mais je recherche les meilleures pratiques en Python.

2 votes

Triage COMMENT FAIRE pour ceux qui recherchent plus d'informations sur le tri en Python.

1 votes

En dehors de operator.attrgetter('nom_attribut'), vous pouvez également utiliser des foncteurs comme clé comme object_list.sort(key=my_sorting_functor('my_key')), en laissant intentionnellement de côté l'implémentation.

1584voto

Triptych Points 70247
# 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)

En savoir plus trier par clés .

2 votes

Pas de problème. En fait, si muhuk a raison et qu'il s'agit d'une liste d'objets Django, vous devriez considérer sa solution. Cependant, pour le cas général du tri des objets, ma solution est probablement la meilleure pratique.

58 votes

Pour les grandes listes, vous obtiendrez de meilleures performances en utilisant operator.attrgetter('count') comme clé. Il s'agit simplement d'une forme optimisée (de niveau inférieur) de la fonction lambda dans cette réponse.

7 votes

Merci pour cette excellente réponse. S'il s'agit d'une liste de dictionnaires et que 'count' est l'une de ses clés, il faut la modifier comme suit : ut.sort(key=lambda x : x['count'], reverse=True)

100voto

tzot Points 32224

Une façon qui peut être la plus rapide, surtout si votre liste a beaucoup d'enregistrements, est d'utiliser operator.attrgetter("count") . Cependant, cela pourrait fonctionner sur une version de Python antérieure à celle de l'opérateur, et il serait bon d'avoir un mécanisme de secours. Vous pourriez donc faire ce qui suit :

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 votes

Ici, j'utiliserais le nom de variable "keyfun" au lieu de "cmpfun" pour éviter toute confusion. La méthode sort() accepte également une fonction de comparaison via l'argument cmp=.

0 votes

Cela ne semble pas fonctionner si l'objet a des attributs ajoutés dynamiquement, (si vous avez fait self.__dict__ = {'some':'dict'} après le __init__ méthode). Je ne vois pas pourquoi ce serait différent, cependant.

0 votes

@tutuca : Je n'ai jamais remplacé l'instance. __dict__ . Notez que "un objet ayant des attributs ajoutés dynamiquement" et "définir les attributs d'un objet" sont des expressions qui ne sont pas utilisées dans le présent document. __dict__ attribut" sont des concepts presque orthogonaux. Je dis cela parce que votre commentaire semble impliquer que le fait de définir l'attribut __dict__ est une exigence pour l'ajout dynamique d'attributs.

79voto

Jose M Vidal Points 3456

Le lecteur remarquera que la méthode key= :

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

est plusieurs fois plus rapide que l'ajout d'opérateurs de comparaison riches aux objets. J'ai été surpris de lire cela (page 485 de "Python in a Nutshell"). Vous pouvez le confirmer en effectuant des tests sur ce petit programme :

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

Mes tests, très minimes, montrent que le premier tri est plus de 10 fois plus lent, mais le livre dit qu'il n'est qu'environ 5 fois plus lent en général. La raison pour laquelle ils disent cela est due à l'algorithme de tri hautement optimisé utilisé dans python ( timsort ).

Néanmoins, il est très étrange que .sort(lambda) soit plus rapide que le bon vieux .sort(). J'espère qu'ils corrigeront cela.

2 votes

Définition de __cmp__ est équivalent à appeler .sort(cmp=lambda) pas .sort(key=lambda) donc ce n'est pas bizarre du tout.

0 votes

@tzot a tout à fait raison. Le premier tri doit comparer les objets les uns aux autres, encore et encore. Le deuxième tri n'accède à chaque objet qu'une seule fois pour extraire sa valeur de comptage, puis il effectue un simple tri numérique qui est hautement optimisé. Une comparaison plus juste serait longList2.sort(cmp = cmp) . Je l'ai essayé et il s'est comporté presque comme le .sort() . (Aussi : notez que le paramètre de tri "cmp" a été supprimé dans Python 3).

1 votes

cmp a été déprécié dans Python 3 : docs.python.org/3/howto/

71voto

jpp Points 83462

Approche orientée objet

Une bonne pratique consiste à faire de la logique de tri des objets, le cas échéant, une propriété de la classe plutôt que de l'incorporer dans chaque instance où le tri est requis.

Cela permet de garantir la cohérence et de supprimer le code passe-partout.

Au minimum, vous devez préciser __eq__ y __lt__ opérations pour que cela fonctionne. Il suffit alors d'utiliser 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]

3 votes

C'est ce que je cherchais ! Pourriez-vous nous indiquer une documentation qui explique en détail pourquoi __eq__ y __lt__ sont les exigences minimales de mise en œuvre ?

4 votes

@FriendFX, je crois que c'est sous-entendu par ce : •The sort routines are guaranteed to use __lt__() when making comparisons between two objects...

2 votes

@FriendFX : Voir portingguide.readthedocs.io/fr/latest/comparaisons.html pour la comparaison et le tri

40voto

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X