3

Consider following code snippet:

class C(object):
    a = 0
    b = 1
    seq = [1, 2, 4, 16, 17]
    list_comp = [a if v%2 else b for v in seq]
    gen_comp = (a if v%2 else b for v in seq)

Code above in interpreted fine. Printing object bound to class variables results in:

print C.list_comp  #  [0, 1, 1, 1, 0]
print C.gen_comp  #  <generator object <genexpr> at ...>

Sad part is - attempt to retrieve value from generator results in NameError:

next(C.gen_comp)  # NameError: global name 'a' is not defined

Expected behavior should be similar to list comprehension - it should yield 5 values and raise StopIteration on each next next() call.

What makes a difference here? How names are resolved in each case and why discrepancy occures?

Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • The difference is that list comprehensions now have their own scope in Python 3 similar to a generator expression. Also explained here: https://docs.python.org/2/reference/executionmodel.html – Ashwini Chaudhary Oct 13 '15 at 08:58

1 Answers1

1

The issue is that generator expressions run in their own namespace , hence they do not have access to names in class scope (class variables like a or b).

This is given in PEP 227 -

Names in class scope are not accessible. Names are resolved in the innermost enclosing function scope. If a class definition occurs in a chain of nested scopes, the resolution process skips class definitions.

Hence you get the NameError when trying to access the class variable in the generator expression.

A way to workaround this would be to access the required values through the class like C.a or C.b . Since the expressions inside the generator expression are only executed when next() is called on it, we can be sure that C class would have been defined by then. Example -

>>> class C(object):
...     a = 0
...     b = 1
...     seq = [1, 2, 4, 16, 17]
...     list_comp = [a if v%2 else b for v in seq]
...     gen_comp = (C.a if v%2 else C.b for v in seq)
... 
>>> next(C.gen_comp)
0
>>> next(C.gen_comp)
1
>>> next(C.gen_comp)
1
>>> next(C.gen_comp)
1
>>> next(C.gen_comp)
0

Note, the same issue occurs for list comprehension in Python 3.x, since in Python 3.x , list comprehensions have their own scopes. See Accessing class variables from a list comprehension in the class definition for more details.

Community
  • 1
  • 1
Anand S Kumar
  • 88,551
  • 18
  • 188
  • 176