While upgrading code from Py2 to Py3, I ran into an oddity I can't quite explain. A list comprehension in a class definition can reference other class level variables in both python 2 and 3 as part of the for
clause. However, if the variables are part of the if
clause they throw a NameError
in python 3 but work just fine in python 2.
I found a few related questions but they don't quite seem to explain the issue. Dictionary class attribute that refers to other class attributes in the definition is a similar issue in lambdas, but doesn't seem to be python version dependant. Additonally Why am I getting a NameError in list comprehension (Python)?, but has to do with scope in a debugger rather than a class.
The following code works fine on python 2.7.16, but fails on Python 3.7.4:
class A(object):
a = [1, 2, 3]
b = [n for n in a if n in a]
print(A.b)
In python 2 I get:
[1, 2, 3]
In python 3 I get:
Traceback (most recent call last):
File "list_comprehension_closure.py", line 3, in <module>
class A(object):
File "list_comprehension_closure.py", line 5, in A
b = [n for n in a if n in a]
File "list_comprehension_closure.py", line 5, in <listcomp>
b = [n for n in a if n in a]
NameError: name 'a' is not defined
However, the following works fine in both python 2 and 3 and the only difference is the if
clause:
class A(object):
a = [1, 2, 3]
b = [n for n in a]
print(A.b)
Additionally, the following works in both python 2 and 3 and the only difference is that the comprehension is defined outside of a class
block:
a = [1, 2, 3]
b = [n for n in a if n in a]
print(b)
I know there were some changes to closures and list comprehensions in Python 3, but I am not seeing anything that explains this difference.
Edit: For clarity, I am not looking for a work around. As demonstrated by my last example, I'm aware that moving the variables outside of the class scope I can resolve the issue. However, what I am looking for is an explanation for why this behavior was changed in python 3.