Pesquisa de lista de dicionários em Python

449

Suponha que eu tenho isso:

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

e pesquisando "Pam" como nome, quero recuperar o dicionário relacionado: {name: "Pam", age: 7}

Como conseguir isso?

Hellnar
fonte

Respostas:

510

Você pode usar uma expressão geradora :

>>> 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'}

Se você precisar lidar com o item que não está lá, faça o que o usuário Matt sugeriu em seu comentário e forneça um padrão usando uma API ligeiramente diferente:

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

E para encontrar o índice do item, em vez do item em si, você pode enumerar () a lista:

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)
Frédéric Hamidi
fonte
229
Apenas para economizar um pouco de tempo para mais alguém, se você precisar de um valor padrão no evento "Pam" não está na lista: next ((item para item em dict se item ["name"] == "Pam") (Nenhum)
Matt
1
Que tal [item for item in dicts if item["name"] == "Pam"][0]?
Moberg
3
@Moberg, ainda é uma lista de compreensão, por isso iterará em toda a sequência de entrada, independentemente da posição do item correspondente.
Frédéric Hamidi
7
Isto irá aumentar erro StopIteration se a chave não estiver presente no dicionário
Kishan
3
@Siemkowski: em seguida, adicione enumerate()a gerar um índice de execução: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters
217

Isso me parece 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 (retornado como uma lista no Python 2):

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

Nota: No Python 3, um objeto de filtro é retornado. Portanto, a solução python3 seria:

list(filter(lambda person: person['name'] == 'Pam', people))
PaoloC
fonte
14
Vale ressaltar que esta resposta retorna uma lista com todas as correspondências para 'Pam' em pessoas. Como alternativa, podemos obter uma lista de todas as pessoas que não são 'Pam' alterando o operador de comparação para! =. +1
Onema 12/11/2015
2
Também vale a pena mencionar que o resultado é um objeto de filtro, não uma lista - se você quiser usar coisas como len(), é necessário chamar list()o resultado primeiro. Ou: stackoverflow.com/questions/19182188/…
wasabigeek
@wasabigeek é o que meu Python 2.7 diz: people = [{'name': "Tom", 'age': 10}, {'name': "Mark", 'age': 5}, {'name': "Pam", 'idade': 7}] r = filter (pessoa lambda: pessoa ['name'] == 'Pam', people) type (r) list Assim ré umalist
PaoloC
1
Compreensões lista são considerados mais Pythonic de mapa / filtro / reduzir: stackoverflow.com/questions/5426754/google-python-style-guide
CCI
2
Obter a primeira partida:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz 12/12/19
60

A resposta de Frédéric Hamidi é ótima. No 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'}

Conforme 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
>>>
Mike N
fonte
1
Esta é a melhor resposta para o Python 3.x. Se você precisar de um elemento específico dos dictos, como idade, poderá escrever: next ((item.get ('age')) para o item dict se o item ["name"] == "Pam"), False)
cwhisperer
47

Você pode usar uma compreensão de lista :

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

fonte
4
Isso é bom porque retorna todas as correspondências se houver mais de uma. Não é exatamente o que a pergunta fazia, mas era o que eu precisava! Obrigado!
user3303554
Observe também que isso retorna uma lista!
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
fonte
Ele retornará o primeiro dicionário da lista com o nome fornecido.
Ricky Robinson
5
Apenas para fazer essa rotina muito útil um pouco mais genérica:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James
30

Testei vários métodos para percorrer uma lista de dicionários e retornar os dicionários em que a chave x tem um determinado valor.

Resultados:

  • Velocidade: compreensão da lista> expressão do gerador >> iteração normal da lista >>> filtro.
  • Toda a escala é linear com o número de dictos na lista (tamanho da lista 10x -> tempo 10x).
  • As chaves por dicionário não afetam significativamente a velocidade de grandes quantidades (milhares) de chaves. Por favor, veja este gráfico que eu calculei: https://imgur.com/a/quQzv (nomes dos métodos, veja 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
user136036
fonte
Adicionei a função z () que implementa a seguir, como apontado por Frédéric Hamidi acima. Aqui estão os resultados do perfil do Py.
Leon
10

Para adicionar um pouquinho ao @ FrédéricHamidi.

Caso você não tenha certeza de que existe uma chave na lista de ditados, algo como isso ajudaria:

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

Você já experimentou o pacote pandas? É perfeito para esse tipo de tarefa de pesquisa 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 em uma 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
abby sobh
fonte
7

Esta é uma maneira geral de pesquisar um valor em uma lista de dicionários:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]
ipegasus
fonte
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]

Esta é uma maneira ...

Niclas Nilsson
fonte
1
Eu poderia sugerir [d para x nos nomes se d.get ('name', '') == 'Pam'] ... manipular graciosamente quaisquer entradas em "names" que não tivessem uma chave "name".
Jim Dennis
6

Simplesmente usando a compreensão da lista:

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

Código de 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'}
Teorético
fonte
5

Você pode conseguir isso usando o filtro e os próximos métodos no Python.

filterO método filtra a sequência especificada e retorna um iterador. nextO método aceita um iterador e retorna o próximo elemento na lista.

Para encontrar o elemento,

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)

e a saída é,

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

Nota: O código acima retornará Nonecaso que o nome que estamos procurando não seja encontrado.

Manoj Kumar S
fonte
Isso é muito mais lento que as compreensões de lista.
AnupamChugh
4

Meu primeiro pensamento seria que você poderia querer criar um dicionário desses dicionários ... se, por exemplo, você o procuraria mais do que um pequeno número de vezes.

No entanto, isso pode ser uma otimização prematura. O que haveria de errado 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]
Jim Dennis
fonte
Na verdade, você pode ter um dicionário com um item name = None; mas isso realmente não funcionaria com a compreensão dessa lista e provavelmente não é sensato permitir isso no armazenamento de dados.
Jim Dennis
1
declarações podem ser ignoradas se o modo de depuração estiver desativado.
Bluppfisk 23/06
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
fonte
3

Uma maneira simples de usar a compreensão da lista é, se lfor a lista

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

então

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

Você pode tentar isso:

''' 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'} 
Siddharth Satpathy
fonte
1

Aqui está uma comparação usando iterating throuhg list, usando filter + lambda ou refatorando (se necessário ou válido para o seu caso) seu código para ditar ditados 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: ter claramente um dicionário de dict é a maneira mais eficiente de poder pesquisar nesses casos, em que você sabe que estará pesquisando apenas por IDs. Curiosamente, usar o filtro é a solução mais lenta.

Kőhalmy Zoltán
fonte
0

Você precisa passar por todos os elementos da lista. Não há um atalho!

A menos que em outro lugar você mantenha um dicionário de nomes apontando para os itens da lista, mas precisará cuidar das conseqüências de exibir um elemento da lista.

jimifiki
fonte
No caso de uma lista não classificada e uma chave ausente, esta declaração está correta, mas não em geral. Se a lista é conhecida por ser classificada, todos os elementos não precisam ser iterados. Além disso, se um único registro for atingido e você souber que as chaves são únicas ou requerem apenas um elemento, a iteração pode ser interrompida com o único item retornado.
user25064
veja a resposta de @ user334856
Melih Yıldız '
@ MelihYıldız 'talvez eu não tenha sido claro na minha declaração. Usando um entendimento da lista, o usuário334856 na resposta stackoverflow.com/a/8653572/512225 está percorrendo a lista inteira. Isso confirma minha afirmação. A resposta que você refere é outra maneira de dizer o que escrevi.
jimifiki
0

Encontrei este tópico quando estava procurando uma resposta para a mesma pergunta. Embora perceba que é uma resposta tardia, pensei em contribuir com isso, caso seja útil para outras pessoas:

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.
fonte
0

A maioria (se não todas) das implementações propostas aqui tem duas falhas:

  • Eles assumem que apenas uma chave deve ser passada para a pesquisa, embora possa ser interessante ter mais para ditados complexos
  • Eles assumem que todas as chaves passadas para pesquisa existem nos dictos; portanto, elas não lidam corretamente com o KeyError que ocorre quando não está.

Uma proposta atualizada:

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)

Talvez não seja o mais pitônico, mas pelo menos um pouco mais à prova de falhas.

Uso:

>>> 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}

A essência .

onekiloparsec
fonte