Список пошуку словників Python


449

Припустимо, у мене це:

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

і шукаючи як ім'я "Pam", я хочу отримати пов'язаний словник: {name: "Pam", age: 7}

Як цього досягти?

Відповіді:


513

Ви можете використовувати вираз генератора :

>>> 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
Це призведе до виникнення помилки зупинки, якщо ключ не присутній у словнику
— Кішань

3
@Siemkowski: потім додати enumerate()генерувати біжить індекс: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
— Martijn Pieters

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': "Позначити", 'age': 5}, {'name': "Pam", 'вік': 7}] r = фільтр (лямбда особа: person ['ім'я'] == 'Pam', люди) тип (r) список Так rєlist
— PaoloC

1
Список осягнення вважаються більш віщими , ніж карта / фільтром / зменшити: stackoverflow.com/questions/5426754/google-python-style-guide
— JRC

2
Отримайте перший матч:next(filter(lambda x: x['name'] == 'Pam', dicts))
— xgMz

60

@ Відповідь Фредерика Хаміді чудова. У Python 3.x синтаксис for .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. Якщо вам потрібен певний елемент із диктів, наприклад вік, ви можете написати: next ((item.get ('age') для елемента в диктах, якщо item ["name"] == "Pam"), False)
— cwhisperer

47

Ви можете використати розуміння списку :

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 має певне значення.

Результати:

  • Швидкість: розуміння списку> вираз генератора >> нормальна ітерація списку >>> фільтр.
  • Весь масштаб лінійний із кількістю диктів у списку (10x розмір списку -> 10x час).
  • Клавіші в словнику не впливають істотно на швидкість для великих кількостей (тисяч) клавіш. Будь ласка, дивіться цей графік, який я підрахував: 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 к + записів:

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 ('ім'я', '') == 'Pam'] ... граціозно обробити будь-які записи у "іменах", у яких не було ключа "ім'я".
— Джим Денніс

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}

Примітка. Вищенаведений код повернеться в Noneрегістр, якщо ім’я, яке ми шукаємо, не знайдено.


Це набагато повільніше, ніж розуміння списку.
— 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

Ось порівняння, використовуючи ітераційний список через список, використовуючи фільтр + лямбда або рефакторинг (якщо це потрібно чи дійсний у вашому випадку) ваш код для диктування диктів, а не список диктів

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

Більшість (якщо не всі) реалізованих тут пропозицій мають два недоліки:

  • Вони передбачають, що для пошуку слід передати лише один ключ, хоча для складного диктату може бути цікавим більше
  • Вони припускають, що всі ключі, передані для пошуку, існують у диктах, отже, вони неправильно поводяться з 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
— Melih Yıldız

@ MelihYıldız, можливо, я не був зрозумілий у своїй заяві. Використовуючи список розуміння user334856 у відповідь 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.