Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable Scope In Generators In Classes

I think that I know how variables and generators work in Python well.
However, the following code makes me confused.

from __future__ import print_function

class A(object):
    x = 4
    gen = (x for _ in range(3))

a = A()
print(list(a.gen))

When run the code (Python 2), it says:

Traceback (most recent call last):
  File "Untitled 8.py", line 10, in <module>
    print(list(a.gen))
  File "Untitled 8.py", line 6, in <genexpr>
    gen = (x for _ in range(3))
NameError: global name 'x' is not defined

In Python 3, it says NameError: name 'x' is not defined
but, when I run:

from __future__ import print_function

class A(object):
    x = 4
    lst = [x for _ in range(3)]

a = A()
print(a.lst)

The code doesn't work in Python 3, but it does in Python 2, or in a function like this

from __future__ import print_function

def func():
    x = 4
    gen = (x for _ in range(3))
    return gen

print(list(func()))

This code works well in Python 2 and Python 3 or on the module level

from __future__ import print_function

x = 4
gen = (x for _ in range(3))

print(list(gen))

The code works well in Python 2 and Python 3 too.

Why is it wrong in class?

like image 883
WeizhongTu Avatar asked Nov 28 '16 16:11

WeizhongTu


3 Answers

Because x is a class attribute (static variable), which you access like,

Example

>>> class A(object):
...     x = 4
...     gen = (A.x for _ in range(3))
...
>>> a = A()
>>> list(a.gen)
[4, 4, 4]

Here even gen is another class attribute, which means that,

>>> b = A()
>>> list(b.gen)
[]

This gives empty because the generator has already exhausted.


This happens because the generator is evaluated only when you issue a.gen, when it won't be able to resolve the name x.
like image 80
nu11p01n73R Avatar answered Sep 27 '22 22:09

nu11p01n73R


As stated in other answer, it is True that it happens because it is static variable. But it is not just that property that restricts your code to work. Actual reason is the scope of the variable and the scope in which it executes. For example, create a class as:

class A(object):
    x = 999999
    y = x +1

If you access it's class properties A.x and A.y, it will work. Because, at the time of initializing y, x is replaced the value in expression x+1. As the scope of x was within the class.

However this doesn't happens in the case of generators. i.e. in your example:

class A(object):
    x = 4
    gen = (x for _ in range(3))

When you do list(a.gen), it is executed outside the class (as generators are evaluated during run-time) and checks for the reference of x in the current scope. Since, x it is not initialized in that scope, it throws error.

When you explicitly initialize x=4, it works because now the generator expression has value of x to which it could use.

In order to make your generator expression work, as stated by others you have to define it like:

class A(object):
    x = 4
    gen = (A.x for _ in range(3))
    #      ^ mentioning `A.x` is the value to access
like image 41
Moinuddin Quadri Avatar answered Sep 27 '22 22:09

Moinuddin Quadri


That is because x is a class variable. In python, class variables has to be accessed with self (ex. access from a instance method) or the class name.

class A(object):
    x = 4
    gen = (A.x for _ in range(3))

    def call_me(self):
        print self.x

a = A()
a.call_me()
print list(a.gen)

For more detailed discussion see Static class variables in Python and Why is Python class not recognizing static variable

like image 41
Hossain Muctadir Avatar answered Sep 28 '22 00:09

Hossain Muctadir