Robert McPythons
Robert McPythons

Reputation: 174

Can't use class level variable

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.

Upvotes: 3

Views: 103

Answers (2)

kabanus
kabanus

Reputation: 26005

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.

Upvotes: 2

Shubham Shaswat
Shubham Shaswat

Reputation: 1310

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")

Upvotes: 0

Related Questions