Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3 - Class variable is not defined [duplicate]

Here I'm not able to access the class variable inside a Python's list comprehension.

class Student:
  max_year = 18
  year_choice = [i for i in range(100) if i > max_year]

  def __init__(self, name):
    self.name = name
    print (self.year_choice)

Student('Blah')

But it's working fine in Python 2.

../workspace$ python student.py
[19, 20, 21, 22, 2.... 99]

But getting an error in Python 3.

../workspace$ python student.py
File "student.py", line 18, in <listcomp>
year_choice = [i for i in range(100) if i > max_year]
NameError: name 'max_year' is not defined

from debugging this When I changed below statement

[i for i in range(100) if i > max_year]

to this

[i for i in range(max_year)] # don't look too much into it ;)

working fine. Why I'm not able to access class variable inside if/else list comprehension?

like image 939
ramganesh Avatar asked Nov 10 '17 13:11

ramganesh


People also ask

How to declare a class variable in Python?

A class variable is declared inside of class, but outside of any instance method or __init__() method. By convention, typically it is placed right below the class header and before the constructor method and other methods.

How to use class variable in Python?

The variables that are defined inside the class but outside the method can be accessed within the class(all methods included) using the instance of a class. For Example – self. var_name. If you want to use that variable even outside the class, you must declared that variable as a global.

Are class variables inherited Python?

Classes called child classes or subclasses inherit methods and variables from parent classes or base classes. We can think of a parent class called Parent that has class variables for last_name , height , and eye_color that the child class Child will inherit from the Parent .

What is class variable and instance variable in Python?

A class variable is a variable that defines a particular property or attribute for a class. An instance variable is a variable whose value is specified to the Instance and shared among different instances. 2. We can share these variables between class and its subclasses. We cannot share these variables between classes.


1 Answers

The reason that this line

year_choice = [i for i in range(100) if i > max_year]

works in Python 2 but not in Python 3 is that in Python 3 list comprehensions create a new scope, and the max_year class attribute isn't in that scope. In Python 2, a list comprehension doesn't create a new scope, it runs in the context of the surrounding code. That was originally done for performance reasons, but a lot of people found it confusing, so it was changed in Python 3, bringing list comprehensions into line with generator expressions, and set and dict comprehensions.

AFAIK, there is no simple way in Python 3 to access a class attribute inside a list comprehension that is running in the outer context of a class, rather than inside a method. You can't refer to it with Student.max_year, since at that point the Student class doesn't exist.

However, there really is no point having that list comprehension there anyway. You can create the list you want more compactly, and more efficiently. For example:

class Student(object):
    max_year = 18
    year_choice = list(range(max_year + 1, 100))

    def __init__(self, name):
        self.name = name
        print (self.year_choice)

Student('Blah')

output

[19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

That code produces the same output on Python 2 and Python 3.

I've changed the class signature to

class Student(object):

so that it creates a new-style class in Python 2 (in Python 3 all classes are new-style).


The reason that [i for i in range(max_year)] can get around this restriction is that an iterator is created from range(max_year) which is then passed as the argument to the temporary function which runs the list comprehension. So it's equivalent to this code:

class Student(object):
    max_year = 18
    def _listcomp(iterator):
        result = []
        for i in iterator:
            result.append(i)
        return result

    year_choice = _listcomp(iter(range(max_year + 1, 100)))
    del _listcomp

    def __init__(self, name):
        self.name = name
        print(self.year_choice)

Student('Blah')

Many thanks to Antti Haapala for this explanation.

like image 165
PM 2Ring Avatar answered Oct 22 '22 14:10

PM 2Ring