列表解析式应该是工作中经常使用的一个技巧了,严格意义上讲甚至都不算是一个技巧。但列表解析的背后却隐藏着一些坑,一不小心就会造成难以察觉的bug,举个例子:
x = 1 class Girl: x = 2 print([i * x for i in (1, 2, 3)])
你觉得这段代码执行之后会打印什么呢?可能有人觉得是 [2, 4, 6],其实答案是 [1, 2, 3]。事实上如果全局作用中没有 x 的话,或者我们把外部的 x = 1 给删掉,那么是会报错的。
原因是 Python 的列表解析式具有独立的作用域,我们知道 Python 在变量查找的时候遵循 LEGB 规则,也就是按照本地作用域、闭包、全局作用域、内置作用域的顺序进行查找。
而列表解析式具有自己独立的作用域,也就是内部在查找变量 x 时的本地作用域。但显然当前列表解析式内部并没有定义 x 这个变量,于是会从闭包里面查找,但这里也没有闭包,因此会从全局作用域中查找,发现 x 等于 1。
再举个例子:
age = 20 # 在列表解析的时候 # 我们分别将 15、16、17 赋值给了变量 age data = [age + 1 for age in (15, 16, 17)] # 但列表解析具有独立的作用域 # 因此两个 age 是无关的 print(age) # 20 # 如果改成普通的 for 循环 age = 20 for age in (15, 16, 17): pass # 循环结束之后,age 变成了 17 # 因为这两个 age 位于同一个作用域 # 所以 age 最终会指向 17 print(age) # 17
当然啦,这些都属于非常基础的内容了,要是因为这种问题而被坑了,那只能说明我太蠢了。我被坑的原因是,列表解析式具有独立的作用域这一结论在 Python3 当中是成立的,但在 Python2 当中不成立。
相同的代码,但因为 Python 版本差异而导致执行的结果不同。而当时我的项目因为历史原因是跑在 Python2 上面的,所以就被这个问题给坑了。
以上就是本文的内容(好水的一篇文章,一直犹豫要不要发),因为觉得这个问题比较有意思,于是拿出来分享一下。