0

Note that what I'm after here is deeper understanding of how python works. I am aware that I can use methods or @property and that any practical needs here would be esoteric and odd.

It is my understanding that when a class is initialized in python, its body is executed within a new namespace, then the locals of that namespace are copied into the class definition.

That said, something odd is happening with comprehensions.

class Foo:
  a = (1,2,3)
  b = [2*x for x in a]

This works fine as expected. No errors.

But simply adding an if clause that references a

class Foo:
  a = (1,2,3)
  b = [2*x for x in (2,3,4) if x in a]

Results in

Input In [3], in <listcomp>(.0)
      1 class Foo:
      2   a = (1,2,3)
----> 3   b = [2*x for x in (2,3,4) if x in a]

NameError: name 'a' is not defined

Confusing since a is accessible inside the comprehension. Though I suppose there's a difference between specifying what is being iterated and what happens on an iteration.

But even more confusing, this works fine.

class Foo:
  a = (1,2,3)
  b = []
  for x in (2,3,4):
    if x in a:
      b.append(x)

So what is going on? What is the comprehension doing that a was not accessible? I always thought it just expanded into something of this sort but clearly that's not the case.

George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • @chepner well, the class body *is a new scope*, it just doesn't create an *enclosing scope*. – juanpa.arrivillaga Mar 22 '23 at 18:01
  • 2
    Long story (read the accepted answer in the duplicate target for the elaborate answer): class bodies do not create enclosing scopes. List comprehensions use a function scope. The variables defined in the class body are not accessible in the nested function scope. This is *the same thing* as `a` not being accessible in some method you define `def foo(self): print(a)` would raise a `NameError`, you'd need `self.a` – juanpa.arrivillaga Mar 22 '23 at 18:02
  • No, it's not a scope, it's a *namespace*. Erased my previous comment, but I think (haven't confirmed) the difference that the filter is evaluated in the scope of the implicit anonymous function created by the list comprehension; the generator is not. – chepner Mar 22 '23 at 18:03
  • @chepner ok, I'm fine with that distinction. So everything is evaluated in the anyonymous scope *except* the left-most `for in ` clause, it is essentially passed as an argument to the anonymous function, `_comp()` so if you did `[x for x in a]` that *would* work – juanpa.arrivillaga Mar 22 '23 at 18:05
  • 2
    " I always thought it just expanded into something of this sort but clearly that's not the case." it actually *does* do that, but it uses a *function* to give the list comprehension it's own scope. You can think of it as `def _comp(arg): for x in arg: if x in a: b.append(x); return b` then `_comp((1,2,3))` – juanpa.arrivillaga Mar 22 '23 at 18:07
  • so, if you are interested, use the dissasmbler, `import dis` then check out `dis.dis("[x for x in (1,2,3) if x > 2]")` – juanpa.arrivillaga Mar 22 '23 at 18:08

0 Answers0