Lista Python de procura de dicionários

Assume que tenho isto:

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

e ao procurar " Pam "como nome, quero recuperar o dicionário relacionado: {nome: "Pam", idade: 7}

Como conseguir isso ?

Author: Belphegor, 2011-12-28

16 answers

Pode usar uma expressão do gerador :

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

>>> (item for item in dicts if item["name"] == "Pam").next()
{'age': 7, 'name': 'Pam'}
 307
Author: Frédéric Hamidi, 2013-08-09 22:04:15
Isto parece-me a maneira mais pitónica:
people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

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

(devolvido como uma lista no Python 2):

[{'age': 7, 'name': 'Pam'}]
Nota: no Python 3, é devolvido um objecto filtro.
 116
Author: PaoloC, 2017-12-26 20:21:24
A resposta de Frédéric Hamidi é óptima. Em Python 3.x a sintaxe para .next() mudou ligeiramente. Assim, uma ligeira modificação:
>>> 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'}

Como mencionado nos comentários de @Matt, você pode adicionar um valor padrão como tal:

>>> 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
>>>
 36
Author: Mike N, 2016-11-21 13:57:07

Pode usar uma compreensão da lista :

def search(name, people):
    return [element for element in people if element['name'] == name]
 29
Author: , 2011-12-28 18:30:14
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")
 20
Author: satoru, 2013-06-20 10:10:52

Para adicionar apenas um pouco a @FrédéricHamidi.

No caso de não ter a certeza de que uma chave está na lista de dictos, algo assim ajudaria:

next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)
 8
Author: Drazen Urch, 2015-12-09 23:18:10

Testei vários métodos para passar por uma lista de dicionários e devolver os dicionários onde a chave x tem um certo valor.

Resultados:

  • Velocidade: compreensão da lista > expressão do gerador > > > iteração normal da lista > > > filtro.
  • todas as escalas lineares com o número de dictos na lista (tamanho da lista 10x -> 10x tempo).
  • as chaves por dicionário não afectam significativamente a velocidade para grandes quantidades (milhares) de chaves. Consultar este gráfico I calculou: https://imgur.com/a/quQzv (nomes dos métodos ver abaixo).

Todos os testes feitos com 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

Resultados:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter
 7
Author: user136036, 2018-02-24 00:51:09

Esta é uma forma geral de procurar um valor numa lista de dicionários:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]
 6
Author: ipegasus, 2014-07-19 21:36:02
Já experimentaste o pacote pandas? É perfeito para este tipo de tarefa de busca e otimizado também.
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) ]

Adicionei um pouco de benchmarking abaixo para ilustrar os tempos de execução mais rápidos dos pandas numa escala maior, ou seja, 100k+ entradas:

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
 6
Author: abby sobh, 2016-09-03 20:12:45
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]
Este é um caminho...
 6
Author: Niclas Nilsson, 2016-11-30 08:53:05

O meu primeiro pensamento seria que você poderia considerar a criação de um dicionário destes dicionários ... se, por exemplo, você fosse procurar mais de um pequeno número de vezes.

No entanto, pode ser uma optimização prematura. Qual seria o problema com:
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]
 4
Author: Jim Dennis, 2011-12-28 08:32:23
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'}
 3
Author: robert king, 2011-12-28 09:15:42

usar simplesmente a compreensão da lista:

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

Código Da Amostra:

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'}
 1
Author: Teoretic, 2018-08-13 14:08:49

Aqui está uma comparação usando a lista iterating through, usando filtro+lambda ou refactoring (se necessário ou Válido para o seu caso) o seu código para dict of dicts em vez de Lista 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

E a saída é esta:

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

Conclusão: Claramente ter um dicionário de dicts é a maneira mais eficiente de ser capaz de pesquisar nesses casos, onde você sabe dizer que você vai estar procurando apenas por id's. curiosamente usar filtro é a solução mais lenta.

 0
Author: Kőhalmy Zoltán, 2016-01-16 13:01:36
Encontrei este tópico quando procurava uma resposta para o mesmo. pergunta. Embora perceba que é uma resposta tarde, pensei que ... contribui para o caso de ser útil a mais alguém.
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
 0
Author: Doug R., 2018-01-18 15:12:45
Tens de rever todos os elementos da lista. Não há nenhum atalho!

A menos que em algum outro lugar você mantenha um dicionário dos nomes apontando para os itens da lista, mas então você tem que cuidar das consequências de popping um elemento de sua lista.

 -1
Author: jimifiki, 2011-12-28 08:44:54