3

Let's say I have two scripts:

test_fun1.py

a = 1

def test1():
    print a
    execfile('test_fun2.py')

test1()

test_fun2.py

b = 2    

def print_a():
    print 'a'

def test2():
    print_a()
    print b

test2()

When I run test_fun1.py I get this error:

NameError: global name 'print_a' is not defined

If I exclude the print_a() within test2() both a and b are printed. Why would variables be set to global but functions wouldn't?

HappyPy
  • 9,839
  • 13
  • 46
  • 68
  • You mean both a and b are printed from test_fun2.py? (or could it be from test1())? Also, have you considered just importing external scripts? – sihrc Aug 13 '13 at 18:51
  • 1
    Maybe this can help you? http://stackoverflow.com/questions/2904274/globals-and-locals-in-python-exec – maxicecilia Aug 13 '13 at 18:53
  • @sihrc: a is printed from test1() and b is printed from test2() when print_a() is excluded from the code. – HappyPy Aug 13 '13 at 18:55
  • So, variables aren't "global" then. – sihrc Aug 13 '13 at 18:59
  • @sihrc: what I meant was that I can use a and b inside the functions although they've been defined outside it. I haven't declared them global with the "global" command though. – HappyPy Aug 13 '13 at 19:06
  • Globals only need to be declared `global` if you assign to them. Otherwise, anything you assign to in a function is local, and anything you just read from is a closure variable or global. – user2357112 Aug 13 '13 at 19:10

1 Answers1

1

This is a pretty interesting problem. First a quick workaround, you can provide dictionaries to use as the local and global namespace when calling execfile(), using an empty dictionary for globals works fine:

# test_fun1.py
a = 1

def test1():
    print a
    execfile('test_fun2.py', {})

test1()

Alternatively, if you want the code to execute within the global scope of your module you can use globals() instead of {}.

Now on to why this isn't working... from the documentation on execfile():

If both dictionaries are omitted, the expression is executed in the environment where execfile() is called.

Here "both dictionaries" is referring to the optional globals and locals arguments to execfile(). In this case "the environment where execfile() is called" is the local scope of the function test1() in test_fun1.py. Now it is reasonable to still expect this to work, because it seems like it should be doing something essentially equivalent to the following:

a = 1

def test1():
    print a
    # code executed by execfile
    b = 2    

    def print_a():
        print 'a'

    def test2():
        print_a()
        print b

    test2()

test1()

However there is a note later on in the docs:

Note: The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function execfile() returns. execfile() cannot be used reliably to modify a function’s locals.

So there you have it, there is an explicit warning here that execfile() cannot be used to reliably modify a function's locals, and that is exactly what this code is attempting to do. The b = 2 and def print_a(): ... statements are executed within the scope of test1(), but they are not actually succeeding in adding those names to the namespace so attempting to access those names later on fails.

Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • Thanks for your answer. I'm actually able to use b inside test2() - when the print_a() statement is removed from test2() both a and b are printed, so I can still access b. Does your explanation just apply to functions then? – HappyPy Aug 13 '13 at 21:22
  • Unfortunately the documentation is kind of vague when it says "`execfile()` cannot be used reliably to modify a function's locals". It very well could be that there is a difference here between variable assignment and function definitions, but I don't really know and I am not about to dig through the Python source code to find out. The point here is that you should really be providing a *globals* argument to `execfile()` or the behavior will be inconsistent depending on where the call occurs. – Andrew Clark Aug 13 '13 at 21:28