Undefined global in list generator expression using python3, works with python2, what changes are needed?

前端 未结 1 759
执笔经年
执笔经年 2020-12-09 20:21
class Some(object):
    tokens = [ ... list of strings ... ]
    untokenized = [tokens.index(a) for a in [... some other l         


        
相关标签:
1条回答
  • 2020-12-09 21:23

    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.

    0 讨论(0)
提交回复
热议问题