scope of generators, class variables, resulting in global na

Arnaud Delobelle arnodel at googlemail.com
Wed Feb 24 11:14:00 EST 2010



Nomen Nescio wrote:
> Hello,
>
> Can someone help me understand what is wrong with this example?
>
> class T:
>   A = range(2)
>   B = range(4)
>   s = sum(i*j for i in A for j in B)
>
> It produces the exception:
>
> <type 'exceptions.NameError'>: global name 'j' is not defined

It's due to scoping rules for classes and/or how generator expressions
are compiled.  When a function definition is executed from within the
body of a class, the body of the class doesn't act as an outer scope
for it.  E.g.

class A:
    x = 2
    def f(self): return x

When f is defined (technically, when the closure is made), the name
'x' is not bound to 2 but is considered to be in the global namespace
(unless the class is defined within a function for example).  Now
generator expressions are defined as generator functions so your
example is akin to something like:

class T:
     A = range(2)
     B = range(4)
     def _gen(L):
          for i in L:
              for j in B:
                  yield i*j
     s = sum(_gen(A))

(From memory, I might be wrong on some details)

Now looking at the above, when _gen is defined, B is considered to be
belonging to the global namespace, therefore if it is not defined
there a NameError will be thrown.

Now a safe workaround to this would be:

class T:
   A = range(2)
   B = range(4)
   s = (lambda A=A, B=B: sum(i*j for i in A for j in B))()

The lambda form is evaluated when the body of the class is executed,
binding the names A and B to the objects you want in the generator
expression.

I remember suggesting a while ago that all generator expressions be
implicitely wrapped like the one above in order to avoid such
surprises.  I can't quite remember what the arguments against were,
but you can probably find them in the archives!

--
Arnaud



More information about the Python-list mailing list