16

我有一个字典列表,如下所示:

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

5 回答 5

32

这并不奇怪。对于. itm_ lst它将首先评估过滤器子句。现在,如果过滤子句是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)
于 2017-07-05T07:53:13.443 回答
15

分别看一下你的生成器表达式:

(itm for itm in lst if itm['a']==5)

这将收集列表中的所有项目itm['a'] == 5。到目前为止,一切都很好。

当你调用next()它时,你告诉 Python从那个生成器表达式中生成第一个项目但只有第一个。

因此,当您有条件时itm['a'] == 5,生成器将获取列表的第一个元素,{'a': 5}并对其进行检查。条件为真,因此该项目由生成器表达式生成并由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此处引发 a 的表达式。您可以使用dict.get()来尝试检索值而不引发异常:

>>> lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]
>>> next((itm for itm in lst if itm.get('b') == 6))
{'b': 6}
于 2017-07-05T07:56:33.147 回答
6

如果字典中没有键,显然itm['b']会引发 a 。一种方法是做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))
于 2017-07-05T07:53:02.807 回答
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

于 2017-07-05T10:19:14.563 回答
0

也许你可以试试这个:

next(next((itm for val in itm.values() if val == 6) for itm in lst))

这可能有点棘手,它会生成 twotier generator,因此您需要两个next来获得结果。

于 2017-07-05T08:07:53.250 回答