在Python中搜索字典列表

772

鉴于:

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

如何通过 name == "Pam" 进行搜索,以检索下面对应的字典?

{"name": "Pam", "age": 7}
25个回答

954
你可以使用一个生成器表达式:

你可以使用一个生成器表达式

>>> 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在他的评论中提出的建议提供一个默认值,使用稍微不同的API:

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

要找到列表中元素的索引而不是元素本身,可以使用enumerate()函数对列表进行枚举:

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

311
为了节省其他人的时间,如果你需要在列表中没有"Pam"的情况下使用默认值:next((item for item in dicts if item["name"] == "Pam"), None)其中,涉及到字典(dicts)和名称为“Pam”的项(item),如果找不到该项,则返回默认值None。 - Matt
5
[item for item in dicts if item["name"] == "Pam"][0] 是什么意思? - Moberg
4
@Moberg,这仍然是列表推导式,因此它将遍历整个输入序列,而不管匹配项的位置如何。 - Frédéric Hamidi
10
如果字典中不存在指定的键,就会引发 StopIteration 错误。 - Kishan Mehta
4
enumerate() 添加到代码中以生成运行索引:next(i for i, item in enumerate(dicts) if item["name"] == "Pam") - Martijn Pieters
显示剩余6条评论

322

在我看来,这是最符合Python风格的方法:

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中,返回一个过滤器对象。因此,Python 3的解决方案如下:

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

17
值得注意的是,这个答案返回一个包含所有在people中与“Pam”匹配的结果列表。或者,我们可以通过将比较运算符改为!=来获取所有不是“Pam”的人的列表。+1 - Onema
4
值得一提的是,结果是一个过滤器对象而不是一个列表——如果你想使用像len()这样的函数,你需要先在结果上调用list()。或者可以参考这里:https://dev59.com/LmIk5IYBdhLWcg3wiuqk。 - wasabigeek
1
列表推导式被认为比使用map/filter/reduce更Pythonic: https://dev59.com/-G035IYBdhLWcg3wbPbc - jrc
10
获取第一个匹配项:next(filter(lambda x: x['name'] == 'Pam', dicts)) - xgMz
在测试中,我发现对于我的(相对较小的)对象列表,使用过滤器平均比使用for循环和if语句慢一个数量级。 - Rafael Zasas
显示剩余3条评论

105

@Frédéric Hamidi的回答很好。在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
>>>

6
这是Python 3.x的最佳答案。如果您需要从字典中获取特定元素,例如年龄,可以编写以下代码:next((item.get('age') for item in dicts if item["name"] == "Pam"), False) - cwhisperer

80
你可以使用列表推导式
def search(name, people):
    return [element for element in people if element['name'] == name]

9
这很好,因为如果有多个匹配项,它会返回所有匹配项。虽然不完全符合问题的要求,但这正是我所需要的!谢谢! - user3303554
2
请注意,这将返回一个列表! - Abbas
能否同时传递两个条件?例如,如果 element['name'] == name 并且 element['age'] == age?我尝试了一下,但好像不起作用,第二个条件上说 element 未定义。 - Martynas
@Martynas 是的,这是可能的。不要忘记在函数 def search2(name, age, people): 中添加一个参数 age,也不要忘记传递这个参数 =)。我刚刚尝试了两个条件,它可以工作! - hotenov
无论值是否存在,这将返回一个列表。 - Juls

61

我测试了各种方法来遍历一个字典列表并返回其中键 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(),根据Frédéric Hamidi的指示实现了下一个功能。这是来自Py profile的结果。 - leon
有人知道为什么列表推导式 c() 比简单迭代列表 a() 要快那么多吗? - knowledge_seeker
1
@knowledge_seeker,这可能不是最好的比喻,但可以将生成器看作数据库中的索引,将列表看作数据库中的查询结果。通过筛选必要的数据片段来获取最终结果比获取结果的结果等更快。希望这样说得通。 - tacan

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

它将返回列表中给定名称的第一个字典。 - Ricky Robinson
12
为了让这个非常有用的程序更加通用化:def search(list, key, value): for item in list: if item[key] == value: return item - Jack James

13

您是否尝试过使用pandas包?它非常适合这种搜索任务并且已经做了优化。

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

我在下面添加了一些基准测试,以说明pandas在更大规模(即10万个以上的条目)上的更快运行时间:

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

而method3 = """df.query("name == 'Pam'")""""虽然对于小数据集比方法2稍慢(仍比LC快两个数量级),但在我的机器上对于较大的数据集来说却是两倍的速度。 - emmagras

12

在@FrédéricHamidi的基础上,我只想稍微补充一点。

如果你不确定一个键是否在字典列表中,可以尝试以下方法:

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

7
或者简单地说,item.get("name") == "Pam"。该语句需要翻译成中文且不改变原意,可译为“或者简单地说,item.get("name") == "Pam"”。 - Andreas Haferburg

12

使用列表解析式的一种简单方法是,如果l是列表:

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

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

11
def dsearch(lod, **kw):
    return filter(lambda i: all((i[k] == v for (k, v) in kw.items())), lod)

lod=[{'a':33, 'b':'test2', 'c':'a.ing333'},
     {'a':22, 'b':'ihaha', 'c':'fbgval'},
     {'a':33, 'b':'TEst1', 'c':'s.ing123'},
     {'a':22, 'b':'ihaha', 'c':'dfdvbfjkv'}]



list(dsearch(lod, a=22))

[{'a': 22, 'b': 'ihaha', 'c': 'fbgval'},
 {'a': 22, 'b': 'ihaha', 'c': 'dfdvbfjkv'}]



list(dsearch(lod, a=22, b='ihaha'))

[{'a': 22, 'b': 'ihaha', 'c': 'fbgval'},
 {'a': 22, 'b': 'ihaha', 'c': 'dfdvbfjkv'}]


list(dsearch(lod, a=22, c='fbgval'))

[{'a': 22, 'b': 'ihaha', 'c': 'fbgval'}]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接