Ask for help about class variable scope (Re: Why doesn't a dictionary work in classes?)

eryk sun eryksun at
Thu Dec 27 01:31:43 EST 2018

On 12/26/18, jfong at <jfong at> wrote:
> I saw the code below at stackoverflow. I have a little idea about the scope
> of a class, and list comprehension and generator expressions, but still
> can't figure out why Z4 works and Z5 not. Can someone explain it? (in a
> not-too-complicated way:-)
> class Foo():
>     XS = [15, 15, 15, 15]
>     Z4 = sum(val for val in XS)
>     try:
>         Z5 = sum(XS[i] for i in range(len(XS)))
>     except NameError:
>         Z5 = None
> print(Foo.Z4, Foo.Z5)
>>>> 60 None

Maybe rewriting it with approximately equivalent inline code and
generator functions will clarify the difference:

    class Foo:
        def genexpr1(iterable):
            for val in iterable:
                yield val

        def genexpr2(iterable):
            for i in iterable:
                yield XS[i]

        XS = [15, 15, 15, 15]
        Z4 = sum(genexpr1(XS))
            Z5 = sum(genexpr2(range(len(XS))))
        except NameError:
            Z5 = None

        del genexpr1, genexpr2

    >>> print(Foo.Z4, Foo.Z5)
    60 None

In both cases, an iterable is passed to the generator function. This
argument is evaluated in the calling scope (e.g. range(len(XS))). A
generator expression has a similar implementation, except it also
evaluates the iterator for the iterable to ensure an exception is
raised immediately in the defining scope if it's not iterable. For

    >>> (x for x in 1)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not iterable

genexpr1 is working with local variables only, but genexpr2 has a
non-local reference to variable XS, which we call late binding. In
this case, when the generator code executes the first pass of the loop
(whenever that is), it looks for XS in the global (module) scope and
builtins scope. It's not there, so a NameError is raised.

With late-binding, the variable can get deleted or modified in the
source scope while the generator gets evaluated. For example:

    >>> x = 'spam'
    >>> g = (x[i] for i in range(len(x)))
    >>> next(g)
    >>> del x
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 1, in <genexpr>
    NameError: name 'x' is not defined

    >>> x = 'spam'
    >>> g = (x[i] for i in range(len(x)))
    >>> next(g)
    >>> x = 'eggs'
    >>> list(g)
    ['g', 'g', 's']

More information about the Python-list mailing list