3

I have following code, that doesn't work:

class Node:
    n = 5
    options = [ i / (n-1) for i in range(n)]


print("Success")

Error I am getting is:

    Traceback (most recent call last):
  File "/Users/ikkamens/Library/Preferences/PyCharm2019.2/scratches/counters.py", line 1, in <module>
    class Node:
  File "/Users/ikkamens/Library/Preferences/PyCharm2019.2/scratches/counters.py", line 3, in Node
    options = [ i / (n-1) for i in range(n)]
  File "/Users/ikkamens/Library/Preferences/PyCharm2019.2/scratches/counters.py", line 3, in <listcomp>
    options = [ i / (n-1) for i in range(n)]
NameError: name 'n' is not defined

However, the following changed version works:

class Node:
    n = 5
    options = [ i / 4 for i in range(n)]


print("Success")

Why can I use the class level variable in range expression, but not in (n-1)? Is this an interpreter bug or is there some rule to explain this behavior? I have tried it with 3.8 and 3.6 interpreters.

  • @DeepSpace Are you sure about that dupe target? Seems to be completely irrelevant. OP is missing proper scoping for `n`, which is interpreted as global. It has nothing to do with comprehension variable binding as far as I can tell. Of course, that scope does not exist before the class is instantiated, so this isn't possible AFAIK – kabanus Jan 26 '20 at 13:38
  • @kabanus it is relevant. That code works on Python 2 where the list comprehension was not implemented as a scope-creating anonymous function https://repl.it/repls/BrightLividBaitware – DeepSpace Jan 26 '20 at 13:40
  • @DeepSpace Ah, I see the connection thanks. Hopefully the comment will make it clearer, especially if this is not an "upgraded from Python 2" question, but just a why this does not work question. – kabanus Jan 26 '20 at 13:43
  • 1
    @DeepSpace Maybe to an expert the connection is clear, however I don't understand the cause after reading connected question / answer. – Robert McPythons Jan 26 '20 at 13:49
  • @RobertMcPythons-[a related answer](https://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition). But neither this link or the dupe answers explains why the n in `(n-1)` is inaccessible but the n in `range(n)` is accessible. – DarrylG Jan 26 '20 at 14:43
  • 2
    Probably relevant to this one: https://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition – anishtain4 Jan 26 '20 at 17:12
  • @RobertMcPythons--error in my previous comment. The section in "the related answer" link title *The (small) exception; or, why one part may still work* explains the error in your code. – DarrylG Jan 26 '20 at 17:17

2 Answers2

2

There are two things here, as seen by the discussion under the OP.

First, in Python, there is no default object or class scope, such as the implicit this in C++. So inside a function, your scope is that function, and you do not have "free" name access to variables defined on the object on the class. That is the reason we have to pass self around - we need the environment containing our object (and class) explicitly. This means if we have

class A:
    n = 5

we can only access n in the class level definition, or by explicitly holding the environment:

class A:
    n = 5
    a = range(n) # legal, evaluated in the class definition, where 'A' is defined and 'n' is part of the statement.
    def f():
        x = self.n # legal. As long you do not override self.n same as next
        x = A.n # legal, this is the class variable scope
        x = n # Error - we do not have 'n' for free. Even worse, if 'n' is a global variable, we are using that.

Second, in Python 3, differently from Python 2 a list comprehension became scoped. No more leaking variables, and the body of the class statement isn't really a scope (rather the definition of a namespace), and so not the scope where the anonymous function that implements the comprehension is defined. So

class A:
    n = 5
    x = [n/a for a in range(n)]

might be seen as

class A:
    n = 5
    def _anonymous_function(param):
        return [n/a for a in range(param)]
    x = _anonymous_function(n)

This way, it might be clearer why n iss legal at one place, and not at the other. In the range(n), similar to the first example in the second code snippet in the answer, we are still in the class namespace definition statement, and "see" n. On the other hand, in the comprehension body we are in a scoped (anonymous) environment, and do not see n as it not in the outer (global) scope, since A a namespace definition. This is the reason why it breaks here, as opposed to the other example. In Python 2 this would work.

kabanus
  • 24,623
  • 6
  • 41
  • 74
0

I am not sure why its not work in this way but may be the interpreter think that (n-1) in the list is belong to a local variable inside the list or part of the loop.Although,I am not sure.

You can do this way

class Node:
    global n
    n = 5
    options = [ i / (n-1) for i in range(n)]


print("Success")
Shubham Shaswat
  • 1,250
  • 9
  • 14
  • 1
    Because the list comprehension is implemented using an anonymous function, the value of `n` isn't taken from the namespace in the `class` statement, but from the closest enclosing scope (which is the global scope here). Adding `global n` makes `n` resolve to that scope, not the `class` statement's namespace. – chepner Jan 26 '20 at 19:29
  • @chepner,I thought that would be the case,btw thanks for a clear explanation – Shubham Shaswat Jan 27 '20 at 08:33