Is this a bug, or is it me?

Arnaud Delobelle arnodel at googlemail.com
Sat Jan 19 07:07:58 EST 2008


On Jan 17, 3:55 pm, Peter Otten <__pete... at web.de> wrote:
> Here is a simpler example:
>
> >>> class A:
>
> ...     a = 42
> ...     list(a for _ in "a")
> ...
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "<stdin>", line 3, in A
>   File "<stdin>", line 3, in <genexpr>
> NameError: global name 'a' is not defined
[...]
> So it seems that Python gets puzzled by the extra scope introduced by the
> genexp, i. e. you are seeing an obscure variant of the following
> (expected) behaviour:
>
> >>> class B:
>
> ...     a = 42
> ...     def f(): a
> ...     f()
> ...
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "<stdin>", line 4, in B
>   File "<stdin>", line 3, in f
> NameError: global name 'a' is not defined

This is exactly the problem:

class A:
    a = 42
    list(a for _ in "a")

is in fact compiled as:

class A:
    a = 42
    def _genexpr():
        for _ in "a":
            yield a
    list(_genexpr())

Except that the _genexpr function is not bound to any name, just left
on the stack for list to pick up next.  This can be checked using the
dis module.
Trying it yields exactly the same error:

>>> class A(object):
...     a = 42
...     def _genexpr():
...         for _ in "a":
...             yield a
...     list(_genexpr())
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in A
  File "<stdin>", line 5, in _genexpr
NameError: global name 'a' is not defined

> I think you should file a bug report, though making the genexp recognizing
> the class scope probably isn't worth the effort.

It isn't necessary to do that to fix the problem.  I think that the
semantics of genexprs concerning free variables are confusing (and
this is a good example!).  I suggested a change on python-ideas a
while ago that would solve this particular issue.

In short, all free variables inside a generator expression could be
bound at the creation of the expression, not when it is evaluated.

So for example

((c, f(x)) for x in L)

would be the equivalent of what one could currently write as.

(lambda _c, _f: ((c, f(x) for x in L))(c, f)

so your example would compile to:

class A:
    a = 42
    def _genexpr(_a):
        for _ in "a":
            yield _a
    list(_genexpr(a))

Which would behave as the OP expected (and I think as it is reasonable
to expect if one doesn't know about how genexprs are implemented).

Other reasons why I think this is desirable are exposed (maybe not
very convincingly) in this post to python-ideas.

http://mail.python.org/pipermail/python-ideas/2007-December/001260.html

--
Arnaud




More information about the Python-list mailing list