python生成器对象混淆

时间:2017-08-03 19:49:55

标签: python generator

我对代码的错误感到困惑:

users = [{'id': 1, 'name': 'Number1', 'age': 11},
         {'id': 2, 'name': 'Number2', 'age': 12},
         {'id': 3, 'name': 'Number3', 'age': 13},
         {'id': 4, 'name': 'Number4', 'age': 14}]

_keys = ('name', 'age')

data_by_user_id = {u.get('id'): (u.get(k) for k in _keys) for u in users}

data_by_user_id如下所示:

{1: <generator object <genexpr> at 0x7f3c12c31050>, 2: <generator object <genexpr> at 0x7f3c12c310a0>, 3: <generator object <genexpr> at 0x7f3c12c310f0>, 4: <generator object <genexpr> at 0x7f3c12c31140>}

但迭代后:

for user_id, data in data_by_user_id.iteritems():
    name, age = data
    print user_id, name, age

结果与我的预期不同:

1 Number4 14
2 Number4 14
3 Number4 14
4 Number4 14

任何人都可以解释我在这里做错了什么吗? 我知道我可以使用列表理解而不是生成器但是我试图用我的代码来解决问题

谢谢!

2 个答案:

答案 0 :(得分:3)

你在词典理解陈述中的表达:

(u.get(k) for k in _keys)

生成器表达式。这意味着你构建了一个生成器。生成器是一个可迭代的对象,可以懒惰地计算元素:它不会从u获取元素,它会推迟此操作,直到您在其上调用next(..)来获取下一个元素。所以你构建了这样一本字典。

for循环的正文中,你写道:

name, age = data

data是项目的值。现在这意味着你要求Python&#34; 解包&#34;可迭代的。这将是有效的,因为可迭代的数量与元素的数量与左边的变量数量完全相同,因此在这种情况下为2。因此,您将耗尽发电机并获得迭代器的结果。接下来打印这些元素。

请注意,在for循环之后,字典的所有值都将耗尽生成器,因此您的for循环会产生副作用。为了防止这种情况,您最好实现生成器。

编辑:这里的另一个问题是,你在字典理解中使用u,这不是一个很好的范围。因此,如果更改 u变量,则生成器的结果也会更改。这是有问题的,因为在字典理解结束时,所有生成器都将使用 last 字典。

您可以通过生成本地范围来解决问题:

{u.get('id'): (lambda u=u: (u.get(k) for k in _keys))() for u in users}

现在它生成预期的输出:

>>> users = [{'id': 1, 'name': 'Number1', 'age': 11},
...          {'id': 2, 'name': 'Number2', 'age': 12},
...          {'id': 3, 'name': 'Number3', 'age': 13},
...          {'id': 4, 'name': 'Number4', 'age': 14}]
>>> 
>>> _keys = ('name', 'age')
>>> data_by_user_id = {u.get('id'): (lambda u=u: (u.get(k) for k in _keys))() for u in users}
>>> for user_id, data in data_by_user_id.iteritems():
...     name, age = data
...     print user_id, name, age
... 
1 Number1 11
2 Number2 12
3 Number3 13
4 Number4 14

答案 1 :(得分:2)

正如您可能已经知道的那样,生成器表达式会被懒惰地评估。对dict.get的评估推迟到生成器表达式被消耗,当前范围中的u是您列表中的最后一个字典:

>>> u = {'id': 1, 'name': 'Number1', 'age': 11}
>>> _keys = ('name', 'age')
>>> gen = (u.get(k) for k in _keys)
>>> # update u
>>> u = {'id': 4, 'name': 'Number4', 'age': 14}
>>> list(gen)
['Number4', 14]

一种明显的解决方法是使用列表理解代替。另一种方法,不如第一种方法,是将生成器表达式放在一个函数中,并通过默认参数将u的当前值绑定到该函数:

data_by_user_id = {u.get('id'): lambda x=u: (x.get(k) for k in _keys) for u in users}

for user_id, data in data_by_user_id.iteritems():
    name, age = data()
    print name, age
Number1 11
Number2 12
Number3 13
Number4 14