为什么在统一理解的第二个变量上出现UnboundLocalError?

时间:2018-10-09 11:21:08

标签: python python-3.x scope list-comprehension

我在这里回答了一个问题:comprehension list in python2 works fine but i get an error in python3

OP的错误是对最大范围和索引使用了相同的变量:

npm

这仅是Python-3错误,与为避免在此定义的变量“泄漏”而添加到理解中的范围有关。更改变量名称可以解决此问题。

错误是:

x = 12
y = 10
z = 12
n = 100

ret_list = [ (x,y,z) for x in range(x+1) for y in range(y+1) for z in range(z+1) if x+y+z!=n ]

因为外部全局UnboundLocalError: local variable 'y' referenced before assignment 被本地作用域遮蔽了。

我的问题是:为什么我在y而不是yz上看到错误?

编辑:如果我删除x上的循环,则错误移至x

z

如果我只做一个循环:

>> ret_list = [ (x,y,z) for y in range(y+1) for z in range(z+1) if x+y+z!=n ]
UnboundLocalError: local variable 'z' referenced before assignment

有效。因此,我怀疑第一个ret_list = [ (x,y,z) for y in range(y+1) if x+y+z!=n ] 函数在所有其他表达式之前 求值,而range的值保持不变。但是确切的原因尚待发现。使用Python 3.4.3。

1 个答案:

答案 0 :(得分:6)

reference documentation(重点是我的)中(隐式)描述了此行为。

  

但是,除了最左边的for子句中的可迭代表达式之外,该理解是在单独的隐式嵌套范围中执行的。这样可以确保在目标列表中分配的名称不会“泄漏”到封闭范围内。

     

最左边的for子句中的可迭代表达式在封闭范围内直接求值,然后作为参数传递给隐式[sic]嵌套范围。随后的for子句以及最左边的for子句中的任何过滤条件都不能在封闭范围内进行评估,因为它们可能取决于从最左边的iterable获得的值。例如:[x*y for x in range(10) for y in range(x, x+10)]

这意味着:

list_ = [(x, y) for x in range(x) for y in range(y)]

等同于:

def f(iter_):
    for x in iter_:
        for y in range(y):
            yield x, y

list_ = list(f(iter(range(x))))

由于最左边的可迭代名称x是在封闭范围内而不是嵌套范围内读取的,因此x的这两种用法之间没有名称冲突。 y并非如此,这就是UnboundLocalError发生的原因。

为什么会这样:列表理解是list(<generator expression>)的语法糖,所以它将使用与生成器表达式相同的代码路径(或至少以相同的方式运行) )。生成器表达式在最左边的for子句中评估可迭代表达式,以在生成器表达式更精巧时进行错误处理。考虑以下代码:

y = None                             # line 1
gen = (x + 1 for x in range(y + 1))  # line 2
item = next(gen)                     # line 3

y显然是错误的类型,因此加法将引发TypeError。通过立即评估range(y + 1),第二行而不是第三行会引发类型错误。因此,更容易诊断问题发生的位置和原因。如果它发生在第3行,那么您可能会错误地认为是导致错误的x + 1语句。

有一个错误报告here提到了此行为。由于希望列表理解和生成器表达式具有相同的行为,因此将其解析为“不是bug”。