Liste des dictionnaires Python recherche

449

Supposons que j'ai ceci:

[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

et en recherchant "Pam" comme nom, je veux récupérer le dictionnaire associé: {name: "Pam", age: 7}

Comment y parvenir?

Hellnar
la source

Réponses:

511

Vous pouvez utiliser une expression de générateur :

>>> dicts = [
...     { "name": "Tom", "age": 10 },
...     { "name": "Mark", "age": 5 },
...     { "name": "Pam", "age": 7 },
...     { "name": "Dick", "age": 12 }
... ]

>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

Si vous devez gérer l'élément qui n'est pas là, vous pouvez faire ce que l'utilisateur Matt a suggéré dans son commentaire et fournir une valeur par défaut en utilisant une API légèrement différente:

next((item for item in dicts if item["name"] == "Pam"), None)

Et pour trouver l'index de l'élément, plutôt que l'élément lui-même, vous pouvez énumérer () la liste:

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)
Frédéric Hamidi
la source
230
Juste pour économiser un peu de temps à quelqu'un d'autre, si vous avez besoin d'une valeur par défaut dans l'événement "Pam" n'est tout simplement pas dans la liste: next ((élément pour l'élément en dict si l'élément ["name"] == "Pam") , Aucun)
Matt
1
Et alors [item for item in dicts if item["name"] == "Pam"][0]?
Moberg
3
@Moberg, c'est toujours une compréhension de la liste, donc il itérera sur toute la séquence d'entrée quelle que soit la position de l'élément correspondant.
Frédéric Hamidi
7
Cela déclenchera une erreur d'arrêt si la clé n'est pas présente dans le dictionnaire
Kishan
3
@Siemkowski: puis ajouter enumerate()à générer un index en cours d' exécution: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters
218

Cela me semble de la manière la plus pythonique:

people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

filter(lambda person: person['name'] == 'Pam', people)

résultat (renvoyé sous forme de liste en Python 2):

[{'age': 7, 'name': 'Pam'}]

Remarque: En Python 3, un objet filtre est renvoyé. La solution python3 serait donc:

list(filter(lambda person: person['name'] == 'Pam', people))
PaoloC
la source
14
Il convient de noter que cette réponse renvoie une liste avec toutes les correspondances pour «Pam» chez les personnes, sinon nous pourrions obtenir une liste de toutes les personnes qui ne sont pas «Pam» en changeant l'opérateur de comparaison en! =. +1
Onema du
2
Il convient également de mentionner que le résultat est un objet filtre, pas une liste - si vous souhaitez utiliser des choses comme len(), vous devez d'abord appeler list()le résultat. Ou: stackoverflow.com/questions/19182188/…
wasabigeek
@wasabigeek c'est ce que dit mon Python 2.7: people = [{'name': "Tom", 'age': 10}, {'name': "Mark", 'age': 5}, {'name': "Pam", 'age': 7}] r = filter (lambda person: person ['name'] == 'Pam', people) type (r) list So ris alist
PaoloC
1
Les compréhensions de liste sont considérées comme plus Pythonic que map / filter /
Reduce
2
Obtenez le premier match:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz
60

@ La réponse de Frédéric Hamidi est excellente. Dans Python 3.x, la syntaxe de .next()légèrement modifiée. Ainsi une légère modification:

>>> dicts = [
     { "name": "Tom", "age": 10 },
     { "name": "Mark", "age": 5 },
     { "name": "Pam", "age": 7 },
     { "name": "Dick", "age": 12 }
 ]
>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

Comme mentionné dans les commentaires de @Matt, vous pouvez ajouter une valeur par défaut en tant que telle:

>>> next((item for item in dicts if item["name"] == "Pam"), False)
{'name': 'Pam', 'age': 7}
>>> next((item for item in dicts if item["name"] == "Sam"), False)
False
>>>
Mike N
la source
1
C'est la meilleure réponse pour Python 3.x. Si vous avez besoin d'un élément spécifique des dict, comme l'âge, vous pouvez écrire: next ((item.get ('age') pour l'élément dans les dicts si l'élément ["name"] == "Pam"), False)
cwhisperer
47

Vous pouvez utiliser une liste de compréhension :

def search(name, people):
    return [element for element in people if element['name'] == name]

la source
4
C'est bien car il renvoie toutes les correspondances s'il y en a plusieurs. Pas exactement ce que la question demandait, mais c'est ce dont j'avais besoin! Merci!
user3303554
Notez également que cela renvoie une liste!
Abbas
34
people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

def search(name):
    for p in people:
        if p['name'] == name:
            return p

search("Pam")
satoru
la source
Il renverra le premier dictionnaire de la liste avec le nom donné.
Ricky Robinson
5
Juste pour rendre cette routine très utile un peu plus générique:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James
30

J'ai testé différentes méthodes pour parcourir une liste de dictionnaires et retourner les dictionnaires où la clé x a une certaine valeur.

Résultats:

  • Vitesse: compréhension des listes> expression du générateur >> itération normale des listes >>> filtre.
  • Toutes les échelles sont linéaires avec le nombre de dits dans la liste (taille de liste 10x -> temps 10x)
  • Les clés par dictionnaire n'affectent pas de manière significative la vitesse pour de grandes quantités (milliers) de clés. Veuillez consulter ce graphique que j'ai calculé: https://imgur.com/a/quQzv (noms des méthodes, voir ci-dessous).

Tous les tests effectués avec Python 3.6 .4, W7x64.

from random import randint
from timeit import timeit


list_dicts = []
for _ in range(1000):     # number of dicts in the list
    dict_tmp = {}
    for i in range(10):   # number of keys for each dict
        dict_tmp[f"key{i}"] = randint(0,50)
    list_dicts.append( dict_tmp )



def a():
    # normal iteration over all elements
    for dict_ in list_dicts:
        if dict_["key3"] == 20:
            pass

def b():
    # use 'generator'
    for dict_ in (x for x in list_dicts if x["key3"] == 20):
        pass

def c():
    # use 'list'
    for dict_ in [x for x in list_dicts if x["key3"] == 20]:
        pass

def d():
    # use 'filter'
    for dict_ in filter(lambda x: x['key3'] == 20, list_dicts):
        pass

Résultats:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter
user136036
la source
J'ai ajouté la fonction z () qui implémente ensuite comme indiqué par Frédéric Hamidi ci-dessus. Voici les résultats du profil Py.
leon
10

Pour ajouter un tout petit peu à @ FrédéricHamidi.

Dans le cas où vous n'êtes pas sûr qu'une clé se trouve dans la liste des dictés, quelque chose comme ça aiderait:

next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)
Drazen Urch
la source
ou simplementitem.get("name") == "Pam"
Andreas Haferburg
10

Avez-vous déjà essayé le package pandas? Il est parfait pour ce type de tâche de recherche et optimisé également.

import pandas as pd

listOfDicts = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

# Create a data frame, keys are used as column headers.
# Dict items with the same key are entered into the same respective column.
df = pd.DataFrame(listOfDicts)

# The pandas dataframe allows you to pick out specific values like so:

df2 = df[ (df['name'] == 'Pam') & (df['age'] == 7) ]

# Alternate syntax, same thing

df2 = df[ (df.name == 'Pam') & (df.age == 7) ]

J'ai ajouté un peu de benchmarking ci-dessous pour illustrer les temps d'exécution plus rapides des pandas à plus grande échelle, c'est-à-dire 100 000 entrées et plus:

setup_large = 'dicts = [];\
[dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 })) for _ in range(25000)];\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

setup_small = 'dicts = [];\
dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 }));\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

method1 = '[item for item in dicts if item["name"] == "Pam"]'
method2 = 'df[df["name"] == "Pam"]'

import timeit
t = timeit.Timer(method1, setup_small)
print('Small Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_small)
print('Small Method Pandas: ' + str(t.timeit(100)))

t = timeit.Timer(method1, setup_large)
print('Large Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_large)
print('Large Method Pandas: ' + str(t.timeit(100)))

#Small Method LC: 0.000191926956177
#Small Method Pandas: 0.044392824173
#Large Method LC: 1.98827004433
#Large Method Pandas: 0.324505090714
abby sobh
la source
7

C'est une manière générale de rechercher une valeur dans une liste de dictionnaires:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]
ipegasus
la source
6
names = [{'name':'Tom', 'age': 10}, {'name': 'Mark', 'age': 5}, {'name': 'Pam', 'age': 7}]
resultlist = [d    for d in names     if d.get('name', '') == 'Pam']
first_result = resultlist[0]

C'est une façon ...

Niclas Nilsson
la source
1
Je pourrais suggérer [d pour x dans les noms si d.get ('nom', '') == 'Pam'] ... pour gérer gracieusement toutes les entrées dans "noms" qui n'avaient pas de clé "nom".
Jim Dennis
6

En utilisant simplement la compréhension de la liste:

[i for i in dct if i['name'] == 'Pam'][0]

Exemple de code:

dct = [
    {'name': 'Tom', 'age': 10},
    {'name': 'Mark', 'age': 5},
    {'name': 'Pam', 'age': 7}
]

print([i for i in dct if i['name'] == 'Pam'][0])

> {'age': 7, 'name': 'Pam'}
Teoretic
la source
5

Vous pouvez y parvenir avec l'utilisation du filtre et des méthodes suivantes en Python.

filterLa méthode filtre la séquence donnée et renvoie un itérateur. nextaccepte un itérateur et retourne l'élément suivant dans la liste.

Vous pouvez donc trouver l'élément par,

my_dict = [
    {"name": "Tom", "age": 10},
    {"name": "Mark", "age": 5},
    {"name": "Pam", "age": 7}
]

next(filter(lambda obj: obj.get('name') == 'Pam', my_dict), None)

et la sortie est,

{'name': 'Pam', 'age': 7}

Remarque: Le code ci-dessus retournera au Nonecas où le nom que nous recherchons n'est pas trouvé.

Manoj Kumar S
la source
C'est beaucoup plus lent que les compréhensions de liste.
AnupamChugh
4

Ma première pensée serait que vous souhaitiez peut-être envisager de créer un dictionnaire de ces dictionnaires ... si, par exemple, vous alliez le rechercher plus d'un petit nombre de fois.

Cependant, cela pourrait être une optimisation prématurée. Quel serait le problème avec:

def get_records(key, store=dict()):
    '''Return a list of all records containing name==key from our store
    '''
    assert key is not None
    return [d for d in store if d['name']==key]
Jim Dennis
la source
En fait, vous pouvez avoir un dictionnaire avec un élément name = None; mais cela ne fonctionnerait pas vraiment avec cette compréhension de liste et il n'est probablement pas raisonnable de l'autoriser dans votre magasin de données.
Jim Dennis
1
les assertions peuvent être ignorées si le mode de débogage est désactivé.
bluppfisk
4
dicts=[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

from collections import defaultdict
dicts_by_name=defaultdict(list)
for d in dicts:
    dicts_by_name[d['name']]=d

print dicts_by_name['Tom']

#output
#>>>
#{'age': 10, 'name': 'Tom'}
Robert King
la source
3

Une façon simple d'utiliser les compréhensions de liste est, si lla liste est

l = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

puis

[d['age'] for d in l if d['name']=='Tom']
cvg
la source
2

Vous pouvez essayer ceci:

''' lst: list of dictionaries '''
lst = [{"name": "Tom", "age": 10}, {"name": "Mark", "age": 5}, {"name": "Pam", "age": 7}]

search = raw_input("What name: ") #Input name that needs to be searched (say 'Pam')

print [ lst[i] for i in range(len(lst)) if(lst[i]["name"]==search) ][0] #Output
>>> {'age': 7, 'name': 'Pam'} 
Satpathie de Siddharth
la source
1

Voici une comparaison en utilisant la liste itérative throuhg, en utilisant un filtre + lambda ou en refactorisant (si nécessaire ou valide pour votre cas) votre code pour dicter des dict plutôt qu'une liste de dict

import time

# Build list of dicts
list_of_dicts = list()
for i in range(100000):
    list_of_dicts.append({'id': i, 'name': 'Tom'})

# Build dict of dicts
dict_of_dicts = dict()
for i in range(100000):
    dict_of_dicts[i] = {'name': 'Tom'}


# Find the one with ID of 99

# 1. iterate through the list
lod_ts = time.time()
for elem in list_of_dicts:
    if elem['id'] == 99999:
        break
lod_tf = time.time()
lod_td = lod_tf - lod_ts

# 2. Use filter
f_ts = time.time()
x = filter(lambda k: k['id'] == 99999, list_of_dicts)
f_tf = time.time()
f_td = f_tf- f_ts

# 3. find it in dict of dicts
dod_ts = time.time()
x = dict_of_dicts[99999]
dod_tf = time.time()
dod_td = dod_tf - dod_ts


print 'List of Dictionries took: %s' % lod_td
print 'Using filter took: %s' % f_td
print 'Dict of Dicts took: %s' % dod_td

Et la sortie est la suivante:

List of Dictionries took: 0.0099310874939
Using filter took: 0.0121960639954
Dict of Dicts took: 4.05311584473e-06

Conclusion: Avoir clairement un dictionnaire de dict est le moyen le plus efficace de pouvoir rechercher dans ces cas, où vous savez dire que vous rechercherez uniquement par identifiant. intéressant d'utiliser un filtre est la solution la plus lente.

Kőhalmy Zoltán
la source
0

Vous devez parcourir tous les éléments de la liste. Il n'y a pas de raccourci!

Sauf si ailleurs, vous conservez un dictionnaire des noms pointant vers les éléments de la liste, mais vous devez alors vous occuper des conséquences de l'extraction d'un élément de votre liste.

jimifiki
la source
Dans le cas d'une liste non triée et d'une clé manquante, cette déclaration est correcte, mais pas en général. Si la liste est connue pour être triée, tous les éléments n'ont pas besoin d'être réitérés. De plus, si un seul enregistrement est atteint et que vous savez que les clés sont uniques ou ne nécessitent qu'un seul élément, l'itération peut être interrompue avec l'élément unique retourné.
user25064
voir la réponse de @ user334856
Melih Yıldız
@ MelihYıldız 'peut-être que je n'étais pas clair dans ma déclaration. En utilisant une compréhension de liste, user334856 dans la réponse stackoverflow.com/a/8653572/512225 parcourt toute la liste. Cela confirme ma déclaration. La réponse que vous mentionnez est une autre façon de dire ce que j'ai écrit.
jimifiki
0

J'ai trouvé ce fil lorsque je cherchais une réponse à la même question. Bien que je réalise que c'est une réponse tardive, j'ai pensé que j'y contribuerais au cas où cela serait utile à quelqu'un d'autre:

def find_dict_in_list(dicts, default=None, **kwargs):
    """Find first matching :obj:`dict` in :obj:`list`.

    :param list dicts: List of dictionaries.
    :param dict default: Optional. Default dictionary to return.
        Defaults to `None`.
    :param **kwargs: `key=value` pairs to match in :obj:`dict`.

    :returns: First matching :obj:`dict` from `dicts`.
    :rtype: dict

    """

    rval = default
    for d in dicts:
        is_found = False

        # Search for keys in dict.
        for k, v in kwargs.items():
            if d.get(k, None) == v:
                is_found = True

            else:
                is_found = False
                break

        if is_found:
            rval = d
            break

    return rval


if __name__ == '__main__':
    # Tests
    dicts = []
    keys = 'spam eggs shrubbery knight'.split()

    start = 0
    for _ in range(4):
        dct = {k: v for k, v in zip(keys, range(start, start+4))}
        dicts.append(dct)
        start += 4

    # Find each dict based on 'spam' key only.  
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam) == dicts[x]

    # Find each dict based on 'spam' and 'shrubbery' keys.
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+2) == dicts[x]

    # Search for one correct key, one incorrect key:
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+1) is None

    # Search for non-existent dict.
    for x in range(len(dicts)):
        spam = x+100
        assert find_dict_in_list(dicts, spam=spam) is None
Doug R.
la source
0

La plupart (sinon la totalité) des implémentations proposées ici ont deux défauts:

  • Ils supposent qu'une seule clé doit être transmise pour la recherche, alors qu'il peut être intéressant d'en avoir plus pour un dict complexe
  • Ils supposent que toutes les clés passées pour la recherche existent dans les dict, donc ils ne traitent pas correctement avec KeyError se produisant quand ce n'est pas le cas.

Une proposition mise à jour:

def find_first_in_list(objects, **kwargs):
    return next((obj for obj in objects if
                 len(set(obj.keys()).intersection(kwargs.keys())) > 0 and
                 all([obj[k] == v for k, v in kwargs.items() if k in obj.keys()])),
                None)

Peut-être pas le plus pythonique, mais au moins un peu plus sûr.

Usage:

>>> obj1 = find_first_in_list(list_of_dict, name='Pam', age=7)
>>> obj2 = find_first_in_list(list_of_dict, name='Pam', age=27)
>>> obj3 = find_first_in_list(list_of_dict, name='Pam', address='nowhere')
>>> 
>>> print(obj1, obj2, obj3)
{"name": "Pam", "age": 7}, None, {"name": "Pam", "age": 7}

L' essentiel .

onekiloparsec
la source