Список пошуку словників 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.