Danh sách Python tìm từ điển


449

Giả sử tôi có cái này:

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

và bằng cách tìm kiếm "Pam" làm tên, tôi muốn lấy từ điển liên quan: {name: "Pam", age: 7}

Làm thế nào để đạt được điều này?

Câu trả lời:


510

Bạn có thể sử dụng biểu thức trình tạ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'}

Nếu bạn cần xử lý mục không có ở đó, thì bạn có thể thực hiện những gì người dùng Matt đề xuất trong nhận xét của mình và cung cấp mặc định bằng API hơi khác:

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

Và để tìm chỉ mục của mục, thay vì chính mục đó, bạn có thể liệt kê () danh sách:

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

229
Chỉ để tiết kiệm cho bất kỳ ai khác một chút thời gian, nếu bạn cần một giá trị mặc định trong sự kiện "Pam" chỉ không có trong danh sách: next ((mục cho mục trong dicts nếu mục ["name"] == "Pam") , Không có)
Matt

1
Thế còn [item for item in dicts if item["name"] == "Pam"][0]?
Moberg

3
@Moberg, đó vẫn là một sự hiểu biết danh sách, vì vậy nó sẽ lặp lại trên toàn bộ chuỗi đầu vào bất kể vị trí của mục phù hợp.
Frédéric Hamidi

7
Điều này sẽ gây ra lỗi dừng lại nếu khóa không có trong từ điển
Kishan

3
@Siemkowski: sau đó thêm enumerate()để tạo chỉ mục đang chạy : next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters

217

Điều này đối với tôi theo cách pythonic nhất:

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

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

kết quả (được trả về dưới dạng danh sách trong Python 2):

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

Lưu ý: Trong Python 3, một đối tượng bộ lọc được trả về. Vì vậy, giải pháp python3 sẽ là:

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

14
Đáng chú ý là câu trả lời này trả về một danh sách với tất cả các kết quả khớp cho 'Pam' trong người, thay vào đó, chúng tôi có thể nhận được danh sách tất cả những người không phải là 'Pam' bằng cách thay đổi toán tử so sánh thành! =. +1
Onema

2
Cũng đáng đề cập rằng kết quả là một đối tượng bộ lọc, không phải là một danh sách - nếu bạn muốn sử dụng những thứ như len(), bạn cần phải gọi list()kết quả trước. Hoặc: stackoverflow.com/questions/19182188/ khăn
wasabigeek

@wasabigeek đây là những gì Python 2.7 của tôi nói: 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 ralist
PaoloC

1
Việc hiểu danh sách được coi là nhiều Pythonic hơn so với bản đồ / bộ lọc / giảm: stackoverflow.com/questions/5426754/google-python-style-guide
jrc 11/11/19

2
Nhận trận đấu đầu tiên:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz

60

Câu trả lời của @ Frédéric Hamidi là tuyệt vời. Trong Python 3.x cú pháp .next()thay đổi một chút. Do đó, một sửa đổi nhỏ:

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

Như được đề cập trong các nhận xét của @Matt, bạn có thể thêm một giá trị mặc định như sau:

>>> 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
Đây là câu trả lời tốt nhất cho Python 3.x. Nếu bạn cần một yếu tố cụ thể từ các dicts, như tuổi, bạn có thể viết: next ((item.get ('age') cho mục trong dicts if item ["name"] == "Pam"), Sai)
cwhisperer

47

Bạn có thể sử dụng một danh sách hiểu :

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

4
Điều này là tốt bởi vì nó trả về tất cả các trận đấu nếu có nhiều hơn một. Không chính xác những gì câu hỏi yêu cầu, nhưng đó là những gì tôi cần! Cảm ơn!
dùng3303554

Cũng lưu ý điều này trả về một danh sách!
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")

Nó sẽ trả về từ điển đầu tiên trong danh sách với tên đã cho.
Ricky Robinson

5
Chỉ để làm cho thói quen rất hữu ích này chung chung hơn một chút:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James

30

Tôi đã thử nghiệm các phương pháp khác nhau để đi qua một danh sách các từ điển và trả về các từ điển trong đó khóa x có một giá trị nhất định.

Các kết quả:

  • Tốc độ: hiểu danh sách> biểu thức trình tạo >> lặp lại danh sách bình thường >>> bộ lọc.
  • Tất cả tỷ lệ tuyến tính với số lượng dicts trong danh sách (kích thước danh sách 10 lần -> 10 lần).
  • Các khóa trên mỗi từ điển không ảnh hưởng đáng kể đến tốc độ đối với số lượng lớn (hàng nghìn) khóa. Vui lòng xem biểu đồ này tôi đã tính: https://imgur.com/a/quQzv (tên phương thức xem bên dưới).

Tất cả các thử nghiệm được thực hiện với 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

Các kết quả:

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

Tôi đã thêm hàm z () thực hiện tiếp theo như được chỉ ra bởi Frédéric Hamidi ở trên. Dưới đây là kết quả từ hồ sơ Py.
leon

10

Để thêm một chút xíu vào @ Frédéricoutidi.

Trong trường hợp bạn không chắc chắn một khóa nằm trong danh sách các ký hiệu, một cái gì đó như thế này sẽ giúp ích:

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

hoặc đơn giản hơnitem.get("name") == "Pam"
Andreas Haferburg

10

Bạn đã bao giờ thử gói gấu trúc chưa? Nó hoàn hảo cho loại nhiệm vụ tìm kiếm này và cũng được tối ưu hóa.

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

Tôi đã thêm một chút điểm chuẩn bên dưới để minh họa thời gian chạy nhanh hơn của gấu trúc ở quy mô lớn hơn, tức là 100k + mục:

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

Đây là một cách chung để tìm kiếm một giá trị trong danh sách từ điển:

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]

Đây là một cách ...


1
Tôi có thể đề xuất [d cho x trong tên nếu d.get ('name', '') == 'Pam'] ... để xử lý một cách duyên dáng bất kỳ mục nào trong "tên" không có khóa "tên".
Jim Dennis

6

Đơn giản chỉ cần sử dụng hiểu danh sách:

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

Mã mẫu:

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

Bạn có thể đạt được điều này với việc sử dụng bộ lọc và các phương thức tiếp theo trong Python.

filterphương thức lọc chuỗi đã cho và trả về một iterator. nextphương thức chấp nhận một trình vòng lặp và trả về phần tử tiếp theo trong danh sách.

Vì vậy, bạn có thể tìm thấy các yếu tố bằng cách,

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)

và đầu ra là,

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

Lưu ý: Mã ở trên sẽ trả về Noneincase nếu không tìm thấy tên chúng tôi đang tìm kiếm.


Điều này chậm hơn rất nhiều so với việc hiểu danh sách.
AnupamChugh

4

Suy nghĩ đầu tiên của tôi là bạn có thể muốn xem xét việc tạo ra một từ điển của những từ điển này ... ví dụ, nếu bạn sẽ tìm kiếm nó nhiều hơn một số lần nhỏ.

Tuy nhiên đó có thể là một tối ưu hóa sớm. Điều gì sẽ sai với:

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]

Trên thực tế bạn có thể có một từ điển với một tên = Không có mục nào trong đó; nhưng điều đó sẽ không thực sự hiệu quả với việc hiểu danh sách này và có lẽ nó không lành mạnh để cho phép nó trong kho dữ liệu của bạn.
Jim Dennis

1
xác nhận có thể bị bỏ qua nếu chế độ gỡ lỗi tắt.
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

Một cách đơn giản để sử dụng hiểu danh sách là, nếu llà danh sách

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

sau đó

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

2

Bạn có thể thử điều này:

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

Dưới đây là so sánh bằng cách sử dụng danh sách lặp lại, sử dụng bộ lọc + lambda hoặc tái cấu trúc (nếu cần hoặc hợp lệ với trường hợp của bạn) mã của bạn để ra lệnh cho các lệnh thay vì danh sách các lệnh

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

Và đầu ra là thế này:

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

Kết luận: Rõ ràng có một từ điển các dicts là cách hiệu quả nhất để có thể tìm kiếm trong những trường hợp đó, nơi bạn biết rằng bạn sẽ chỉ tìm kiếm theo id. thú vị khi sử dụng bộ lọc là giải pháp chậm nhất.


0

Bạn phải đi qua tất cả các yếu tố của danh sách. Không có lối tắt!

Trừ khi ở một nơi khác, bạn giữ một từ điển các tên chỉ vào các mục của danh sách, nhưng sau đó bạn phải quan tâm đến hậu quả của việc bật một phần tử từ danh sách của bạn.


Trong trường hợp danh sách chưa được sắp xếp và khóa bị thiếu, tuyên bố này là chính xác, nhưng không phải nói chung. Nếu danh sách được biết là được sắp xếp, tất cả các yếu tố không cần phải lặp đi lặp lại. Ngoài ra, nếu một bản ghi duy nhất được nhấn và bạn biết các khóa là duy nhất hoặc chỉ yêu cầu một phần tử, thì việc lặp lại có thể bị dừng lại với một mục duy nhất được trả về.
user25064

xem câu trả lời của @ user334856
Melih Yıldız '

@ MelihYıldız 'có lẽ tôi đã không rõ ràng trong tuyên bố của mình. Bằng cách sử dụng một danh sách người dùng hiểu được334856 trong câu trả lời stackoverflow.com/a/8653572/512225 sẽ đi qua toàn bộ danh sách. Điều này xác nhận tuyên bố của tôi. Câu trả lời bạn giới thiệu là một cách khác để nói những gì tôi đã viết.
jimifiki

0

Tôi tìm thấy chủ đề này khi tôi đang tìm kiếm một câu trả lời cho cùng một câu hỏi. Trong khi tôi nhận ra rằng đó là một câu trả lời muộn, tôi nghĩ tôi sẽ đóng góp nó trong trường hợp nó hữu ích cho bất kỳ ai khác:

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

0

Hầu hết (nếu không phải tất cả) các triển khai được đề xuất ở đây có hai lỗ hổng:

  • Họ giả sử chỉ có một khóa được thông qua để tìm kiếm, trong khi điều đó có thể thú vị hơn khi có nhiều hơn cho chính tả phức tạp
  • Họ cho rằng tất cả các khóa được thông qua để tìm kiếm tồn tại trong các dicts, do đó họ không xử lý chính xác với KeyError xảy ra khi không.

Một đề xuất cập nhật:

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)

Có thể không phải là pythonic nhất, nhưng ít nhất một chút không an toàn.

Sử dụng:

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

Các ý chính .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.