Lista de búsqueda de diccionarios en Python

449

Supongamos que tengo esto:

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

y al buscar "Pam" como nombre, quiero recuperar el diccionario relacionado: {name: "Pam", age: 7}

¿Cómo lograr esto?

Hellnar
fuente

Respuestas:

511

Puedes usar una expresión generadora :

>>> 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 necesita manejar el elemento que no está allí, puede hacer lo que el usuario Matt sugirió en su comentario y proporcionar un valor predeterminado utilizando una API ligeramente diferente:

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

Y para encontrar el índice del elemento, en lugar del elemento en sí, puede enumerar () la lista:

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)
Frédéric Hamidi
fuente
230
Solo para salvar a alguien más un poco de tiempo, si necesita un valor predeterminado en el caso de que "Pam" simplemente no esté en la lista: siguiente ((elemento por elemento en dictos si elemento ["nombre"] == "Pam") , Ninguno)
Matt
1
¿Qué hay de [item for item in dicts if item["name"] == "Pam"][0]?
Moberg
3
@Moberg, eso sigue siendo una comprensión de la lista, por lo que iterará sobre toda la secuencia de entrada independientemente de la posición del elemento coincidente.
Frédéric Hamidi
77
Esto generará un error de stopiteración si la clave no está presente en el diccionario
Kishan
3
@Siemkowski: a continuación, añadir enumerate()a generar un índice de ejecución: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters
218

Esto me parece la forma más pitónica:

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

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

resultado (devuelto como una lista en Python 2):

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

Nota: En Python 3, se devuelve un objeto de filtro. Entonces la solución python3 sería:

list(filter(lambda person: person['name'] == 'Pam', people))
PaoloC
fuente
14
Vale la pena señalar que esta respuesta devuelve una lista con todas las coincidencias para 'Pam' en las personas, alternativamente, podríamos obtener una lista de todas las personas que no son 'Pam' cambiando el operador de comparación a! =. +1
Onema
2
También vale la pena mencionar que el resultado es un objeto de filtro, no una lista; si desea usar cosas como len(), list()primero debe invocar el resultado. O: stackoverflow.com/questions/19182188/…
wasabigeek
@wasabigeek esto es lo que dice mi 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
Las comprensiones de listas se consideran más Pythonic que map / filter / reduce: stackoverflow.com/questions/5426754/google-python-style-guide
jrc
2
Consigue el primer partido:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz
60

La respuesta de @ Frédéric Hamidi es genial. En Python 3.x, la sintaxis de .next()cambió ligeramente. Por lo tanto, una ligera modificación:

>>> 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 se menciona en los comentarios de @Matt, puede agregar un valor predeterminado 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
fuente
1
Esta es la mejor respuesta para Python 3.x. Si necesita un elemento específico de los dictados, como age, puede escribir: next ((item.get ('age') para el ítem en dicts if item ["name"] == "Pam"), False)
cwhisperer
47

Puedes usar una lista de comprensión :

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

fuente
44
Esto es bueno porque devuelve todas las coincidencias si hay más de una. No es exactamente lo que pedía la pregunta, ¡pero es lo que necesitaba! ¡Gracias!
user3303554
Tenga en cuenta también que esto devuelve una 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
fuente
Devolverá el primer diccionario de la lista con el nombre de pila.
Ricky Robinson
55
Solo para hacer que esta rutina tan útil sea un poco más genérica:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James
30

Probé varios métodos para revisar una lista de diccionarios y devolver los diccionarios donde la clave x tiene un cierto valor.

Resultados:

  • Velocidad: comprensión de la lista> expresión del generador >> iteración normal de la lista >>> filtro.
  • Todas las escalas lineales con el número de dictados en la lista (10x tamaño de la lista -> 10x tiempo).
  • Las claves por diccionario no afectan significativamente la velocidad para grandes cantidades (miles) de claves. Consulte este gráfico que calculé: https://imgur.com/a/quQzv (los nombres de los métodos se muestran a continuación).

Todas las pruebas realizadas con 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
usuario136036
fuente
Agregué la función z () que implementa a continuación como lo señaló Frédéric Hamidi arriba. Aquí están los resultados del perfil Py.
Leon
10

Para agregar solo un poquito a @ FrédéricHamidi.

En caso de que no esté seguro de que una clave esté en la lista de dictados, algo como esto ayudaría:

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

¿Alguna vez has probado el paquete de pandas? Es perfecto para este tipo de tarea de búsqueda y también optimizado.

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) ]

He agregado un poco de evaluación comparativa a continuación para ilustrar los tiempos de ejecución más rápidos de los pandas a mayor escala, es decir, más de 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
fuente
7

Esta es una forma general de buscar un valor en una lista de diccionarios:

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

Niclas Nilsson
fuente
1
Podría sugerir [d para x en los nombres si d.get ('name', '') == 'Pam'] ... para manejar con gracia cualquier entrada en "nombres" que no tuviera una tecla "nombre".
Jim Dennis
6

Simplemente usando la comprensión de la lista:

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

Código de muestra:

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
fuente
5

Puede lograr esto con el uso del filtro y los siguientes métodos en Python.

filter El método filtra la secuencia dada y devuelve un iterador. nextEl método acepta un iterador y devuelve el siguiente elemento de la lista.

Para que pueda encontrar el 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)

y la salida es,

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

Nota: El código anterior volverá en Nonecaso de que no se encuentre el nombre que estamos buscando.

Manoj Kumar S
fuente
Esto es mucho más lento que la lista de comprensiones.
AnupamChugh
4

Mi primer pensamiento sería que tal vez quieras considerar crear un diccionario de estos diccionarios ... si, por ejemplo, lo estuvieras buscando más de un pequeño número de veces.

Sin embargo, eso podría ser una optimización prematura. Qué estaría mal con:

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
fuente
En realidad, puede tener un diccionario con un nombre = Ninguno elemento en él; pero eso realmente no funcionaría con esta comprensión de la lista y probablemente no sea sensato permitirlo en su almacén de datos.
Jim Dennis
1
las afirmaciones pueden omitirse si el modo de depuración está desactivado.
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
fuente
3

Una forma simple de usar las comprensiones de listas es, si les la lista

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

entonces

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

Puedes probar esto:

''' 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
fuente
1

Aquí hay una comparación usando iteración a través de la lista, usando filtro + lambda o refactorizando (si es necesario o válido para su caso) su código para dictar dictados en lugar de la lista de dictados

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

Y la salida es esta:

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

Conclusión: Claramente, tener un diccionario de dictados es la forma más eficiente de poder buscar en esos casos, donde sabe que buscará solo por identificación. Curiosamente, usar filtro es la solución más lenta.

Kőhalmy Zoltán
fuente
0

Tienes que revisar todos los elementos de la lista. ¡No hay un atajo!

A menos que en otro lugar mantenga un diccionario de los nombres que apuntan a los elementos de la lista, pero luego debe ocuparse de las consecuencias de que aparezca un elemento de su lista.

jimifiki
fuente
En el caso de una lista sin clasificar y una clave faltante, esta afirmación es correcta, pero no en general. Si se sabe que la lista está ordenada, no es necesario repetir todos los elementos. Además, si se golpea un solo registro y sabe que las claves son únicas o solo requieren un elemento, entonces la iteración puede detenerse con el único elemento devuelto.
user25064
vea la respuesta de @ user334856
Melih Yıldız '
@ MelihYıldız 'tal vez no estaba claro en mi declaración. Al usar una lista de comprensión, user334856 en la respuesta stackoverflow.com/a/8653572/512225 está revisando toda la lista. Esto confirma mi declaración. La respuesta que refieres es otra forma de decir lo que escribí.
jimifiki
0

Encontré este hilo cuando estaba buscando una respuesta a la misma pregunta. Si bien me doy cuenta de que es una respuesta tardía, pensé en contribuir en caso de que sea útil para alguien más:

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

La mayoría (si no todas) las implementaciones propuestas aquí tienen dos defectos:

  • Asumen que solo se debe pasar una clave para la búsqueda, mientras que puede ser interesante tener más para dict complejos
  • Asumen que todas las claves pasadas para la búsqueda existen en los dictados, por lo tanto, no tratan correctamente con KeyError que ocurre cuando no es así.

Una propuesta actualizada:

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)

Quizás no sea el más pitónico, pero al menos un poco más seguro.

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}

La esencia .

onekiloparsec
fuente