9
class Some(object):
    tokens = [ ... list of strings ... ]
    untokenized = [tokens.index(a) for a in [... some other list of strings ...]]
    ... etc ...
some = Some()

This works fine with Python2.7. However python3 says:

Traceback (most recent call last):
File "./test.py", line 17, in <module>
    class Some(object):
File "./test.py", line 42, in Some
    untokenized = [tokens.index(a) for a in [... some other list of strings ...]]
File "./test.py", line 42, in <listcomp>
    untokenized = [tokens.index(a) for a in [... some other list of strings ...]]
NameError: global name 'tokens' is not defined

Though I can work around the problem, I really would like to know what's difference here between Python2 and Python3. I've read python 2->3 changes documents, but I was not able to identify any description which is related to my problem. Also 2to3 tool does not complain anything in my code.

By the way, though I can't recall the situation now, but I had something similar with python2 only too (I haven't even tried this with 3), I thought this should work (within a class):

def some_method(self):
    return {a: eval("self." + a) for a in dir(self) if not a.startswith("_")}

However it causes python2 saying: NameError: name 'self' is not defined I haven't tried this with python3 yet, but for example this works:

[eval("self." + a) for a in dir(self) if not a.startswith("_")]

If I change the relevant part of the previous example to this one (ok the example itself is a bit stupid, but it shows my problem at least). Now I am very curious, why self seems not to be defined for this first example but it is for the second? It seems with dicts, I have similar problem that my original question is about, but with list generator expression it works, but not in python3. Hmmm ...

After my python2 -> 3 problem I mentioned this, since all of these seems to be about the problem that something is not defined according to python interpreter (and maybe the second part of my question is unrelated?). I feel quite confused now. Please enlighten me about my mistake (since I am sure I missed something of course).

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
LGB
  • 728
  • 1
  • 9
  • 20
  • 5
    ick. `eval` is not needed, just use `getattr(self, a)` instead. – Martijn Pieters Jul 26 '12 at 12:35
  • 3
    There's a [python-dev thread](http://mail.python.org/pipermail/python-dev/2009-February/086287.html) that explains how this is caused by classes not having a lexical scope in py3k. – Wooble Jul 26 '12 at 12:44
  • 1
    @Wooble classes don't have a lexical scope in Python2 either; the difference is that in Python 3 list comprehensions *do* have a lexical scope. – ecatmur Jul 26 '12 at 13:42

1 Answers1

14

As Wooble says, the issue is that classes don't have a lexical scope (actually, in either Python 2 or Python 3). Instead, they have a local namespace that does not constitute a scope. This means that expressions within the class definition have access to the content of the namespace:

class C:
    a = 2
    b = a + 2    # b = 4

but scopes introduced within the body of the class do not have access to its namespace:

class C:
    a = 2
    def foo(self):
        return a    # NameError: name 'a' is not defined, use return self.__class__.a

The difference between Python 2 and Python 3 is that in Python 2 list comprehensions do not introduce a new scope:

[a for a in range(3)]
print a    # prints 2

whereas in Python 3 they do:

[a for a in range(3)]
print(a)    # NameError: name 'a' is not defined

This was changed in Python 3 for a couple of reasons, including to make list comprehensions behave the same way as generator-expressions (genexps); (a for a in range(3)) has its own scope in both Python 2 and Python 3.

So, within the body of a class, a Python 2 genexp or a Python 3 listcomp or genexp introduces a new scope and therefore does not have access to the class-definition local namespace.

The way to give the genexp/listcomp access to names from the class-definition namespace is to introduce a new scope, using a function or a lambda:

class C:
    a = 2
    b = (lambda a=a: [a + i for i in range(3)])()


The eval issue

The issue with your eval example is that eval by default evaluates its argument in the local scope; because Python 2 list comprehensions have the above behaviour of sharing the enclosing scope the eval can access the method scope, but a genexp or Python 3 listcomp local scope only has whatever the compiler can tell is required from the enclosing scope (since a genexp/listcomp scope is a closure):

def bar(x):
    return list(eval('x') + x for i in range(3))
bar(5)  # returns [10, 10, 10]

def baz(x):
    return list(eval('x') for i in range(3))
baz(5)  # NameError: name 'x' is not defined

As Martijn says, instead of eval you should use getattr.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Wow, thanks, really great explanation, I am not so in python yet to figure out _all_ of these alone, it seems :) Anyway, sticking at the very first example of my original question: what would be the nice version of that which works well with both pf python 2 and 3? Now I am using python 2 only, but I'd like to write code which will work with py3k too with the as little modifications as possible. Thanks again. – LGB Jul 26 '12 at 13:57
  • @LGB use the `lambda` technique shown above, or separate out your processing into its own function. – ecatmur Jul 26 '12 at 14:00
  • "The way to give the genexp/listcomp access to names from the class-definition namespace is to introduce a new scope, using a function or a lambda" -- but that's really annoying! is it just me, or does this make listcomps/genexps way less useful? – hwjp May 30 '14 at 08:52