One function calling another defined in the same file being exec'd

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Thu Jan 7 20:57:53 EST 2010


On Thu, 07 Jan 2010 17:47:13 -0500, Mitchell L Model wrote:

> Next I call dofile() on a slightly more complex file, in which one
> function calls  another function defined earlier in the same file.
> 
> ################################
> def fn1(val):
>      return sum(range(val))
> 
> def fn2(arg):
>      return fn1(arg)
> 
> result = fn2(5)
> ################################
> 
> This produces a surprise:
> 
>      NameError: global name 'fn1' is not defined
> 
> [1] How is it that fn2 can be called from the top-level of the script
> but fn1 cannot be called from fn2?


This might help you to see what's going on. Define your own cut-down 
version of the global namespace, and a local namespace, and a string to 
execute:


myglobals = {'__builtins__': None, 'globals': globals, 'locals': locals, 
'print': print}
mylocals = {'result': None}
s = """def f():
    print("Globals inside f:", globals())
    print("Locals inside f:", locals())

print("Globals at the top level:", globals())
print("Locals at the top level:", locals())
f()
"""

exec(s, myglobals, mylocals)



And this is what you should see:


Globals at the top level: {'__builtins__': None, 'print': <built-in 
function print>, 'globals': <built-in function globals>, 'locals': <built-
in function locals>}
Locals at the top level: {'result': None, 'f': <function f at 0xb7ddeeac>}
Globals inside f: {'__builtins__': None, 'print': <built-in function 
print>, 'globals': <built-in function globals>, 'locals': <built-in 
function locals>}
Locals inside f: {}


Does that clarify what's going on?


> [2] Is this correct behavior or is there something wrong with Python
> here?

This certainly surprised me too. I don't know if it is correct or not, 
but it goes back to at least Python 2.5.



> [3] How should I write a file to be exec'd that defines several
> functions that call each other, as in the trivial fn1-fn2 example above?

My preference would be to say, don't use exec, just import the module. 
Put responsibility on the user to ensure that they set a global "result", 
and then just do this:

mod = __import__('user_supplied_file_name')
result = mod.result


But if that's unworkable for you, then try simulating the namespace setup 
at the top level of a module. The thing to remember is that in the top 
level of a module:

>>> globals() is locals()
True

so let's simulate that:


myglobals = {'result': None}  # You probably also want __builtins__
s = """def f():
    return g() + 1

def g():
    return 2

result = f()
"""
exec(s, myglobals, myglobals)
myglobals['result']


This works for me.


-- 
Steven



More information about the Python-list mailing list