在Python 2.7中使用嵌套生成器表达式

5
在一个appengine应用程序中,我想构建一个对象列表的所有属性名称集合。这应该相当简单:
users = security.User.all().fetch(1000)
props = set([k for k in u.properties().keys() for u in users])

然而,上面的代码会导致错误:
File "/Users/paulkorzhyk/Projects/appengine-flask-template/app/app.py", line 70, in allusers
props = set([k for k in u.properties().keys() for u in users])
UnboundLocalError: local variable 'u' referenced before assignment

在调试器中进行了一些实验后,我注意到添加一个虚拟表达式可以修复代码:
users = security.User.all().fetch(1000)
[u.properties().keys() for u in users]
props = set([k for k in u.properties().keys() for u in users])

对我来说,这很反直觉,为什么原始版本在Python 2.7中失败?为什么在中间添加一个“无用”的表达式可以解决问题?


根据这个答案 https://dev59.com/JGsz5IYBdhLWcg3wNVES 关联应该从左到右,因此重新排列循环语句应该可以纠正。 - Ifthikhan
2个回答

7

只需更改评估的顺序即可。

props = set([k for k in u.properties().keys() for u in users])

to

props = set([k for u in users for k in u.properties().keys() ])

此处无需使用列表推导式,而是可以使用生成器表达式和集合推导式。
props = set(k for u in users for k in u.properties().keys() )

评估顺序是从右到左的

在您原始的表达式中

set([k for k in u.properties().keys() for u in users])

可以理解为:

for k in u.properties().keys(): # Here u is undefined
    for u in users:
        #what ever

使用虚拟表达式的有趣现象是列表推导式会泄露变量,这会导致u在全局范围内泄漏。
因此,
[u.properties().keys() for u in users]

泄漏了全局范围内的 u,这使得
set([k for k in u.properties().keys() for u in users])

合法的

以下示例展示了列表推导式如何泄露变量

>>> del i
>>> foo = [range(1,10) for _ in range(10)]
>>> globals()['i']

Traceback (most recent call last):
  File "<pyshell#84>", line 1, in <module>
    globals()['i']
KeyError: 'i'
>>> [i for i in foo]
[[1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9]]
>>> globals()['i']
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 

谢谢解释,非常有帮助。这个变量泄漏是一个 bug 吗? - Boycott Russia
这不是一个错误,而是一个特性,它在 Python 2.3 之后的版本中被移除了。请参考 http://docs.python.org/2/reference/expressions.html#id20。 - Abhijit

1
你的原始示例失败的原因是你把for子句放错了顺序。在列表/生成器推导式中,for子句的顺序与嵌套的for循环的顺序相同。也就是说,最左边的是最外层的,最右边的是最内层的。改变for子句的顺序即可使其正常工作。
虚拟表达式改变行为的原因是虚拟表达式是一个列表推导式,在Python 2中,列表推导式(不像生成器推导式)会将它们的循环变量泄漏到封闭的作用域中。泄漏的u允许第二个示例运行,但它并没有做你想象中的事情,因为你在props = ...行中的for子句仍然是错误的顺序。它只循环一次u的值,即虚拟表达式中的最后一个u值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接