Python список словарей поиска


450

Предположим, у меня есть это:

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

и поиск «Pam» в качестве имени, я хочу получить соответствующий словарь: {name: "Pam", age: 7}

Как этого добиться?

Ответы:


516

Вы можете использовать выражение генератора :

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

Если вам нужно обработать элемент, которого там нет, то вы можете сделать то, что предложил пользователь Мэтт в своем комментарии, и задать значение по умолчанию, используя немного другой API:

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

И чтобы найти индекс элемента, а не сам элемент, вы можете перечислить () список:

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

230
Просто чтобы сэкономить кому-то еще немного времени, если вам нужно значение по умолчанию в событии "Pam", которого просто нет в списке: next ((элемент для элемента в комментариях, если item ["name"] == "Pam") , Нет)
Мэтт

1
Как насчет [item for item in dicts if item["name"] == "Pam"][0]?
Моберг

3
@Moberg, это все еще понимание списка, поэтому оно будет перебирать всю входную последовательность независимо от положения соответствующего элемента.
Фредерик Хамиди

7
Это вызовет ошибку остановки итераций, если ключ отсутствует в словаре
Kishan

3
@Siemkowski: затем добавить enumerate()генерировать бегущий индекс: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Мартин Питерс

218

Это выглядит для меня наиболее питоническим образом:

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

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

результат (возвращается в виде списка в Python 2):

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

Примечание. В Python 3 возвращается объект фильтра. Таким образом, решение python3 будет:

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

14
Стоит отметить, что этот ответ возвращает список всех совпадений с «Pam» в людях, или же мы можем получить список всех людей, которые не являются «Pam», изменив оператор сравнения на! =. +1
Онема

2
Также стоит упомянуть, что результатом является объект фильтра, а не список - если вы хотите использовать такие вещи, как len(), вам нужно list()сначала вызвать результат. Или: stackoverflow.com/questions/19182188/…
wasabigeek

@wasabigeek это то, что говорит мой Python 2.7: people = [{'name': "Tom", 'age': 10}, {'name': "Mark", "age": 5}, {'name': "Pam", 'age': 7}] r = фильтр (лямбда-персона: person ['name'] == 'Pam', people) тип (r) список Так rже как иlist
PaoloC


2
Получить первый матч:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz

60

@ Ответ Фредерика Хамиди отличный. В Python 3.x синтаксис для .next()слегка изменился. Таким образом, небольшое изменение:

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

Как упоминалось в комментариях @Matt, вы можете добавить значение по умолчанию как таковое:

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

1
Это лучший ответ для Python 3.x. Если вам нужен конкретный элемент из dicts, например age, вы можете написать: next ((item.get ('age') для item в dicts, если item ["name"] == "Pam"), False)
cwhisperer

48

Вы можете использовать понимание списка :

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

4
Это хорошо, потому что он возвращает все совпадения, если их больше одного. Не совсем то, о чем спрашивал вопрос, но это то, что мне было нужно! Спасибо!
user3303554

Обратите внимание, что это возвращает список!
Аббас

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

Он вернет первый словарь в списке с заданным именем.
Рики Робинсон

5
Просто чтобы сделать эту очень полезную процедуру немного более общей:def search(list, key, value): for item in list: if item[key] == value: return item
Джек Джеймс

30

Я протестировал различные методы, чтобы просмотреть список словарей и вернуть словари, в которых ключ x имеет определенное значение.

Результаты:

  • Скорость: понимание списка> выражение генератора >> обычная итерация списка >>> фильтр.
  • Вся шкала линейная с количеством диктов в списке (10х размер списка -> 10х раз).
  • Количество ключей в словаре не влияет существенно на скорость для большого количества (тысяч) ключей. Пожалуйста, посмотрите график, который я рассчитал: https://imgur.com/a/quQzv (названия методов см. Ниже).

Все тесты выполнены на 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

Результаты:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter

Я добавил функцию z (), которая реализует следующую, как указано выше Фредериком Хамиди. Вот результаты из профиля Py.
Леон

10

Чтобы добавить чуть-чуть к @ FrédéricHamidi.

Если вы не уверены, что ключ находится в списке диктов, может помочь что-то вроде этого:

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

или простоitem.get("name") == "Pam"
Андреас Хефербург

10

Вы когда-нибудь пробовали пакет с пандами? Он идеально подходит для такой задачи поиска и оптимизирован.

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

Ниже я добавил немного бенчмаркинга, чтобы проиллюстрировать более быстрое время выполнения панд в более широком масштабе, т.е. 100 000 записей:

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

7

Это общий способ поиска значения в списке словарей:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]

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]

Это один из способов ...


1
Я мог бы предложить [d для x в именах, если d.get ('name', '') == 'Pam'] ... изящно обрабатывать любые записи в "именах", которые не имеют ключа "name".
Джим Деннис

6

Просто используя понимание списка:

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

Образец кода:

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

5

Вы можете достичь этого с помощью фильтра и следующих методов в Python.

filter Метод фильтрует заданную последовательность и возвращает итератор. nextМетод принимает итератор и возвращает следующий элемент в списке.

Таким образом, вы можете найти элемент,

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)

и вывод есть,

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

Примечание. Приведенный выше код вернет Noneincase, если имя, которое мы ищем, не найдено.


Это намного медленнее, чем списки.
AnupamChugh

4

Моей первой мыслью было бы, что вы, возможно, захотите создать словарь этих словарей ... если, например, вы собираетесь искать его более чем несколько раз.

Однако это может быть преждевременной оптимизацией. Что было бы не так с:

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]

На самом деле вы можете иметь словарь с именем = Нет элемента в нем; но на самом деле это не сработает с этим пониманием списка, и, вероятно, не разумно разрешать его в вашем хранилище данных.
Джим Деннис

1
утверждения могут быть пропущены, если режим отладки выключен.
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'}

3

Один простой способ использования списочных представлений - это если lсписок

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

тогда

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

2

Вы можете попробовать это:

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

1

Вот сравнение с использованием итерации по списку, с использованием filter + lambda или рефакторингом (если необходимо или допустимо для вашего случая) вашего кода, чтобы диктовать его, а не список.

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

И вывод такой:

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

Вывод: очевидно, что наличие словаря словарей является наиболее эффективным способом поиска в тех случаях, когда, как вы знаете, вы будете искать только по идентификаторам. Интересно, что использование фильтра является самым медленным решением.


1

Большинство (если не все) реализации, предложенные здесь, имеют два недостатка:

  • Они предполагают, что для поиска будет передан только один ключ, хотя может быть интересно иметь больше для сложного
  • Они предполагают, что все ключи, переданные для поиска, существуют в dicts, поэтому они неправильно обрабатывают KeyError, возникающий, когда это не так.

Обновленное предложение:

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)

Возможно, не самый питонический, но, по крайней мере, немного более безопасный.

Применение:

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

Суть .


0

Вы должны пройти через все элементы списка. Там нет ярлыка!

Если где-то еще вы не ведете словарь имен, указывающих на элементы списка, но тогда вы должны позаботиться о последствиях выталкивания элемента из вашего списка.


В случае несортированного списка и отсутствующего ключа это утверждение верно, но не в целом. Если известно, что список отсортирован, все элементы не нужно перебирать. Кроме того, если нажата одна запись и вы знаете, что ключи уникальны или требуется только один элемент, то итерация может быть остановлена ​​с возвращением одного элемента.
user25064

см. ответ @ user334856
Мелих Йылдыз

@ MelihYıldız 'может быть, я не совсем понял в своем заявлении. Используя понимание списка, пользователь334856 в ответе stackoverflow.com/a/8653572/512225 просматривает весь список. Это подтверждает мое утверждение. Ответ, на который вы ссылаетесь, - это еще один способ сказать, что я написал.
Jimifiki

0

Я нашел эту ветку, когда искал ответ на тот же вопрос. Хотя я понимаю, что это поздний ответ, я решил внести его, если он пригодится кому-то еще:

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.