我有一个字典列表,如下所示:
lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]
我写了一个生成器表达式,如:
next((itm for itm in lst if itm['a']==5))
现在奇怪的是,虽然这适用于'a'
的键值对
它会在下次为所有其他表达式抛出错误。
表达:
next((itm for itm in lst if itm['b']==6))
错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
KeyError: 'b'
答案 0 :(得分:33)
这并不奇怪。对于itm
中的每个lst
。它将首先评估过滤器子句。现在,如果filter子句是itm['b'] == 6
,它将尝试从该字典中获取'b'
键。但由于第一个字典没有这样的密钥,因此会引发错误。
对于第一个过滤器示例,这不是问题,因为第一个字典具有 'a'
键。 next(..)
仅对生成器发出的第一个元素感兴趣。所以它永远不会要求过滤更多的元素。
您可以在此处使用.get(..)
使查找更加安全无虞:
next((itm for itm in lst if itm.get('b',None)==6))
如果字典没有这样的密钥,.get(..)
部分将返回None
。由于None
不等于6,因此过滤器将省略第一个字典并进一步查看另一个匹配。请注意,如果未指定默认值,则None
是默认值,因此等效语句为:
next((itm for itm in lst if itm.get('b')==6))
我们也可以省略生成器的括号:只有当有多个参数时,我们才需要这些额外的括号:
next(itm for itm in lst if itm.get('b')==6)
答案 1 :(得分:15)
分别看一下你的生成器表达式:
(itm for itm in lst if itm['a']==5)
这将收集列表itm['a'] == 5
中的所有项目。到目前为止一切都很好。
当您在其上调用next()
时,您告诉Python从该生成器表达式生成第一个项。但只有第一个。
因此,当您具有条件itm['a'] == 5
时,生成器将获取列表的第一个元素{'a': 5}
并对其执行检查。条件为true,因此该项由生成器表达式生成并由next()
返回。
现在,当您将条件更改为itm['b'] == 6
时,生成器将再次获取列表的第一个元素{'a': 5}
,并尝试使用键b
获取元素。这将失败:
>>> itm = {'a': 5}
>>> itm['b']
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
itm['b']
KeyError: 'b'
它甚至没有机会查看第二个元素,因为它在尝试查看第一个元素时已经失败。
要解决此问题,您必须避免使用可在此处引发KeyError
的表达式。您可以使用dict.get()
尝试检索值而不会引发异常:
>>> lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]
>>> next((itm for itm in lst if itm.get('b') == 6))
{'b': 6}
答案 2 :(得分:6)
如果字典中没有itm['b']
键,KeyError
显然会引发'b'
。一种方法是做
next((itm for itm in lst if 'b' in itm and itm['b']==6))
如果您不想在任何词典中None
,那么您可以将其简化为
next((itm for itm in lst if itm.get('b')==6))
(这与6
进行比较后的效果相同,但如果与None
进行比较,则会产生错误的结果。
或安全地使用占位符
PLACEHOLDER = object()
next((itm for itm in lst if itm.get('b', PLACEHOLDER)==6))
答案 3 :(得分:1)
实际上,您的结构是词典列表。
>>> lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]
为了更好地了解您的第一个病情,请尝试以下方法:
>>> gen = (itm for itm in lst if itm['a'] == 5)
>>> next(gen)
{'a': 5}
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
KeyError: 'a'
每次调用next
时,都会处理下一个元素并返回一个项目。也...
next((itm for itm in lst if itm['a'] == 5))
创建一个未分配给任何变量的生成器,处理lst
中的第一个元素,看到键'a'
确实存在,并返回该项。然后垃圾收集发生器。不抛出错误的原因是因为lst
中的第一项确实包含此密钥。
因此,如果您将密钥更改为第一项不包含的内容,则会收到您看到的错误:
>>> gen = (itm for itm in lst if itm['b'] == 6)
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
KeyError: 'b'
嗯,已经讨论过的一个解决方案是使用dict.get
函数。这是使用defaultdict
的另一种选择:
from collections import defaultdict
from functools import partial
f = partial(defaultdict, lambda: None)
lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]
lst = [f(itm) for itm in lst] # create a list of default dicts
for i in (itm for itm in lst if itm['b'] == 6):
print(i)
打印出来:
defaultdict(<function <lambda> at 0x10231ebf8>, {'b': 6})
如果密钥不存在,defaultdict
将返回None
。
答案 4 :(得分:0)
也许你可以试试这个:
next(next((itm for val in itm.values() if val == 6) for itm in lst))
这可能有点棘手,它会生成两层generator
,因此您需要两个next
才能获得结果。