web-dev-qa-db-fra.com

Python: retirer le dictionnaire de la liste

Si j'ai une liste de dictionnaires, dites:

[{'id': 1, 'name': 'paul'},
{'id': 2, 'name': 'john'}]

et je voudrais supprimer le dictionnaire avec id de 2 (ou nom john), quel est le moyen le plus efficace de procéder par programme (c’est-à-dire que je ne connais pas l’index de l’entrée dans la liste ne peut pas simplement être sauté).

44
john a macdonald
thelist[:] = [d for d in thelist if d.get('id') != 2]

Edit : comme certains doutes ont été exprimés dans un commentaire sur les performances de ce code (certains sont basés sur une incompréhension des caractéristiques de performance de Python, certains sur la supposition au-delà des spécifications données qu'il existe exactement un dict dans la liste avec une valeur de 2 pour la clé 'id'), je souhaite offrir une assurance sur ce point.

Sur une vieille machine Linux, mesurez ce code:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); thelist[:] = [d for d in thelist if d.get('id') != 2]"
10000 loops, best of 3: 82.3 usec per loop

dont environ 57 microsecondes pour random.shuffle (nécessaire pour que l'élément à supprimer ne soit PAS TOUJOURS au même endroit ;-) et 0,65 microsecondes pour la copie initiale (celui qui s'inquiète de l'impact des copies superficielles de listes Python sur les performances est le plus évidemment pour déjeuner ;-), nécessaire pour éviter de modifier la liste originale dans la boucle (donc chaque jambe de la boucle a quelque chose à supprimer ;-).

Lorsque l'on sait qu'il ne reste qu'un élément à supprimer, il est possible de le localiser et de le supprimer encore plus rapidement:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); where=(i for i,d in enumerate(thelist) if d.get('id')==2).next(); del thelist[where]"
10000 loops, best of 3: 72.8 usec per loop

(utilisez la méthode intégrée next plutôt que la méthode .next si vous utilisez Python 2.6 ou une version supérieure, bien sûr) - mais ce code échoue si le nombre de dict qui remplissent la condition de suppression n'en est pas exactement un. En généralisant cela, nous avons:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]"
10000 loops, best of 3: 23.7 usec per loop

comme on le sait, il est possible de supprimer le brassage car il existe déjà trois dict équidistants à supprimer. Et la liste, inchangée, se comporte bien:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]"
10000 loops, best of 3: 23.8 usec per loop

totalement au coude à coude, même avec seulement 3 éléments de 99 à enlever. Avec des listes plus longues et plus de répétitions, cela vaut encore plus, bien sûr:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]"
1000 loops, best of 3: 1.11 msec per loop
$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]"
1000 loops, best of 3: 998 usec per loop

Dans l’ensemble, il ne vaut évidemment pas la peine de déployer la subtilité de la création et de l’inversion de la liste des indices à supprimer, par rapport à la compréhension parfaitement simple et évidente de la liste, pour éventuellement gagner 100 nanosecondes dans un petit cas - et perdre 113 microsecondes dans un plus grand ;-) Éviter ou critiquer des solutions simples, directes et parfaitement adaptées aux performances (comme la compréhension de liste pour cette classe générale de problèmes "supprimer certains éléments d'une liste") est un exemple particulièrement déplaisant de la thèse bien connue de Knuth et Hoare selon laquelle "l'optimisation prématurée est la racine de tout mal dans la programmation "! -)

87
Alex Martelli

Ce n’est pas vraiment une réponse (car je pense que vous en avez déjà une assez bonne), mais ... avez-vous envisagé de disposer d’un dictionnaire de <id>:<name> au lieu d’une liste de dictionnaires?

7
fortran
# assume ls contains your list
for i in range(len(ls)):
    if ls[i]['id'] == 2:
        del ls[i]
        break

Sera probablement plus rapide que les méthodes de compréhension de liste en moyenne, car elle ne parcourt pas toute la liste si elle trouve l’élément en question plus tôt.

2
Imagist

Vous pouvez essayer ce qui suit: 

a = [{'id': 1, 'name': 'paul'},
     {'id': 2, 'name': 'john'}]

for e in range(len(a) - 1, -1, -1):
    if a[e]['id'] == 2:
        a.pop(e)

Si vous ne parvenez pas à afficher le texte depuis le début, et à la fin, cela ne gâchera pas la boucle for.

1
zeroDivisible

Supposons que votre version de python soit 3.6 ou supérieure, et que vous n'avez pas besoin de l'élément supprimé, cela coûterait moins cher ...

Si les dictionnaires de la liste sont uniques:

for i in range(len(dicts)):
    if dicts[i].get('id') == 2:
        del dicts[i]
        break

Si vous souhaitez supprimer tous les éléments correspondants:

for i in range(len(dicts)):
    if dicts[i].get('id') == 2:
        del dicts[i]

Vous pouvez également vous assurer que l’objet id ne soulèvera pas l’erreur de clé, quelle que soit la version de Python.

si dict [i] .get ('id', aucun) == 2

0
nixmind

Vous pouvez essayer quelque chose dans le sens suivant:

def destructively_remove_if(predicate, list):
      for k in xrange(len(list)):
          if predicate(list[k]):
              del list[k]
              break
      return list

  list = [
      { 'id': 1, 'name': 'John' },
      { 'id': 2, 'name': 'Karl' },
      { 'id': 3, 'name': 'Desdemona' } 
  ]

  print "Before:", list
  destructively_remove_if(lambda p: p["id"] == 2, list)
  print "After:", list

Sauf si vous construisez quelque chose qui ressemble à un index sur vos données, je ne pense pas que vous puissiez faire mieux que de créer une "table. Brute-force" sur toute la liste. Si vos données sont triées selon la clé que vous utilisez, vous pourrez peut-être utiliser le module bisect pour rechercher plus rapidement l’objet que vous recherchez.

0
Dirk