Search code examples
pythonclassglobal-variablesinstanceself

Why must a new global variable be created to reference the current class instance in "exec"?


I have a class that contains ~20 methods, and in def __init__(self, ...): I have to call many of these methods (~9) but I didn't want to have to call each individual method one by one.

So I took the easy way out and created two list list comprehensions, that use exec to call each method:

[exec("self.create%s()" % x) for x in "ArticleObjects SeriesObjects ArticleList SearchList".split(" ")]
[exec("self.compile%sPage(self)" % x) for x in "About Screenshots Search Contact Articles".split(" ")]

When I ran this code using python3 filename.py I got an error, that read:

NameError: name 'self' is not defined

Through trial and error I found that; in order to get this code to work I had to create a copy of self called instance and make the new instance variable a global variable and then call the method using ClassName.methodName(instance) instead of self.methodName():

With the working code being:

global instance; instance = self
[exec("ClassNamecreate%s(instance)" % x) for x in "ArticleObjects SeriesObjects ArticleList SearchList".split(" ")]
[exec("ClassName.compile%sPage(instance)" % x) for x in "About Screenshots Search Contact Articles".split(" ")]

Why is this? Why is the self variable undefined in exec despite it being available to the scope that exec is being called in?

Update: I'm using Python 3.6.7


Solution

  • There's lots of good suggestions here for how to avoid the exec statement (which is generally bad), but to answer your question about why this happens, it's got more to do with the list comprehension. List comprehensions create a new scope, and when you call exec without a globals or locals argument, it uses the locals() function:

    Note: The default locals act as described for function locals() below

    Source

    Here you can see what the results of the locals() function look like from within a list comprehension:

    class Sample:
        def __init__(self):
            k = 4
            print(locals())
            exec("print(locals())")
            [print(locals()) for x in range(1)]
            [exec("print(locals())") for x in range(1)]
    Sample()
    

    output:

    {'k': 4, 'self': <__main__.Sample object at 0x00000000030295C0>}
    {'k': 4, 'self': <__main__.Sample object at 0x00000000030295C0>}
    {'x': 0, '.0': <range_iterator object at 0x00000000030019F0>}
    {'x': 0, '.0': <range_iterator object at 0x00000000030019F0>}
    

    So, locals() is the same inside or outside the exec. It's the list comprehension that changes it. Only, when you're outside an exec statement, the interpreter can fall past the locals of the list comprehension and find self in the outer scope. No such luck once you call exec.