ChatGPT解决这个技术问题 Extra ChatGPT

Python 字典列表搜索

假设我有这个:

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

通过搜索“Pam”作为名称,我想检索相关字典:{name: "Pam", age: 7}

如何做到这一点?


M
Mike 'Pomax' Kamermans

您可以使用 generator expression

>>> 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 suggested in his comment 并使用稍微不同的 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)

只是为了节省其他人一点时间,如果您在“Pam”事件中需要一个默认值,只是不在列表中: next((item for item in dicts if item["name"] == "Pam") , 没有任何)
[item for item in dicts if item["name"] == "Pam"][0] 呢?
@Moberg,这仍然是一个列表理解,所以它会遍历整个输入序列,而不管匹配项的位置。
如果字典中不存在键,这将引发停止迭代错误
@Siemkowski:然后添加 enumerate() 以生成运行索引:next(i for i, item in enumerate(dicts) if item["name"] == "Pam")
D
David Schumann

这在我看来是最蟒蛇的方式:

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

值得注意的是,此答案返回一个列表,其中包含人员中“Pam”的所有匹配项,或者我们可以通过将比较运算符更改为!= 来获取所有不是“Pam”的人的列表。 +1
另外值得一提的是,结果是一个过滤器对象,而不是一个列表——如果你想使用 len() 之类的东西,你需要先在结果上调用 list()。或者:stackoverflow.com/questions/19182188/…
@wasabigeek 这就是我的 Python 2.7 所说的: people = [ {'name': "Tom", 'age': 10}, {'name': "Mark", 'age': 5}, {'name': "Pam", 'age': 7} ] r = filter(lambda person: person['name'] == 'Pam', people) type(r) list 所以 rlist
列表推导被认为比 map/filter/reduce 更 Pythonic:stackoverflow.com/questions/5426754/google-python-style-guide
获得第一场比赛:next(filter(lambda x: x['name'] == 'Pam', dicts))
f
fragilewindows

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

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

您可以使用 list comprehension

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

这很好,因为如果有多个匹配项,它会返回所有匹配项。不完全是问题所要求的,但这是我需要的!谢谢!
另请注意,这会返回一个列表!
是否可以通过两个条件?比如 if element['name'] == name and element['age'] == age?我试过了,但似乎不起作用,说元素在第二种情况下未定义。
@Martynas 是的,这是可能的。不要忘记将参数 age 添加到函数 def search2(name, age, people): 并且不要忘记传递此参数 =)。我刚刚尝试了两个条件,它的工作原理!
u
user136036

我测试了各种方法来遍历字典列表并返回 key 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

我添加了实现 next 的函数 z(),正如上面 Frédéric Hamidi 所指出的那样。以下是 Py 配置文件的结果。
R
Ricky Robinson
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")

它将返回列表中具有给定名称的第一个字典。
只是为了让这个非常有用的例程更通用一点:def search(list, key, value): for item in list: if item[key] == value: return item
a
abby sobh

你试过 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 在更大范围内更快的运行时间,即 100k+ 条目:

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 快 2 个数量级),但在我的机器上速度是其两倍对于更大的数据集
D
Drazen Urch

向@FrédéricHamidi 添加一点点。

如果您不确定某个键是否在 dicts 列表中,这样的事情会有所帮助:

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

或者干脆item.get("name") == "Pam"
T
Teoretic

简单地使用列表理解:

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

c
cvg

使用列表推导的一种简单方法是,如果 l 是列表

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

然后

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

i
ipegasus

这是在字典列表中搜索值的一般方法:

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

M
Manoj Kumar S

您可以通过在 Python 中使用 filter 和 next 方法来实现这一点。

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


这比列表推导要慢得多。
i
internety
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'}]

N
Niclas Nilsson
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]

这是一种方式...


我可能会建议 [d for x in names if d.get('name', '') == 'Pam'] ... 优雅地处理“names”中没有“name”键的任何条目。
R
Rusty Rob
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'}

S
Siddharth Satpathy

你可以试试这个:

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

F
Federico Baù

将接受的答案放入函数中以便于重用

def get_item(collection, key, target):
    return next((item for item in collection if item[key] == target), None)

或者也作为 lambda

   get_item_lambda = lambda collection, key, target : next((item for item in collection if item[key] == target), None)

结果

    key = "name"
    target = "Pam"
    print(get_item(target_list, key, target))
    print(get_item_lambda(target_list, key, target))

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

如果密钥可能不在目标字典中,请使用 dict.get 并避免 KeyError

def get_item(collection, key, target):
    return next((item for item in collection if item.get(key, None) == target), None)

get_item_lambda = lambda collection, key, target : next((item for item in collection if item.get(key, None) == target), None)

J
Jim Dennis

我的第一个想法是,您可能要考虑创建这些词典的词典……例如,如果您要搜索它的次数不止一次。

然而,这可能是一个过早的优化。会有什么问题:

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]

实际上,您可以有一个字典,其中包含 name=None 项;但这不适用于此列表理解,并且在您的数据存储中允许它可能不明智。
如果调试模式关闭,则可能会跳过断言。
o
onekiloparsec

这里提出的大多数(如果不是全部)实现有两个缺陷:

他们假设只传递一个键进行搜索,而对于复杂的 dict 有更多可能会很有趣

他们假设为搜索传递的所有键都存在于字典中,因此它们不能正确处理发生的 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)

也许不是最pythonic,但至少更安全一点。

用法:

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

gist


K
Kőhalmy Zoltán

这是使用迭代通过列表,使用过滤器+lambda或重构(如果需要或对您的情况有效)您的代码到dicts而不是dicts列表的比较

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

结论:显然,拥有字典字典是能够在这些情况下进行搜索的最有效方式,您知道您将仅通过 id 进行搜索。有趣的是,使用过滤器是最慢的解决方案。


M
Mike

我会像这样创建一个字典:

names = ["Tom", "Mark", "Pam"]
ages = [10, 5, 7]
my_d = {}

for i, j in zip(names, ages):
    my_d[i] = {"name": i, "age": j}

或者,使用与发布的问题完全相同的信息:

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

for d in info_list:
    my_d[d["name"]] = d

然后你可以做 my_d["Pam"] 并得到 {"name": "Pam", "age": 7}


j
jimifiki

您必须遍历列表的所有元素。没有捷径!

除非您在其他地方保留指向列表项的名称字典,否则您必须注意从列表中弹出元素的后果。


在未排序列表和缺少键的情况下,此语句是正确的,但通常不正确。如果已知列表已排序,则不需要遍历所有元素。此外,如果命中单个记录并且您知道键是唯一的或只需要一个元素,则迭代可能会停止并返回单个项目。
查看@user334856 的回答
@MelihYıldız'也许我的陈述并不清楚。通过在答案 stackoverflow.com/a/8653572/512225 中使用列表理解 user334856 正在遍历整个列表。这证实了我的说法。您提到的答案是另一种表达我所写内容的方式。
D
Deacon

当我在寻找同一问题的答案时,我发现了这个线程。虽然我意识到这是一个迟到的答案,但我想我会贡献它以防它对其他人有用:

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

m
manimino

RangeIndex 将比列表理解或过滤器更快。

from rangeindex import RangeIndex

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

ri = RangeIndex(dicts, {'name': str, 'age': int})
ri.find("name == 'Pam' and age == 7")

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