生成器表达式Python

时间:2017-07-05 07:50:05

标签: python dictionary generator generator-expression

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

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'

5 个答案:

答案 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才能获得结果。