Search code examples
pythonipythonread-eval-print-loopexecfile

Functions defined in dynamically-loaded scripts cannot refer to each other


I'm trying to load functions from a script dynamically when I'm inside an ipython interactive shell. For example, suppose I have a python script like this:

# script.py
import IPython as ip

def Reload():
  execfile('routines.py', {}, globals())

if __name__ == "__main__":
  ip.embed()

Suppose the file routines.py is like this:

# routines.py
def f():
  print 'help me please.'
def g():
  f()

Now if I run the script script.py, I'll be entering the interactive shell. If I type the following, my call to g() works:

execfile('routines.py')
g()

However, if I type the following, the call to g() fails:

Reload()
g()

I will get an error message saying that "global name f is not defined.", although I can still see that f and g are in the output when I type globals() in the interactive shell.

What's the difference of these two?

UPDATE:

The following works, however it's not a preferred solution so I would like to have a better solution for the problem above.

If I change script.py to:

# script.py
import IPython as ip

def Reload():
  execfile('routines.py')

if __name__ == "__main__":
  ip.embed()

And change routines.py to:

# routines.py

global f
global g

def f():
  print 'help me please.'
def g():
  f()

Then if I call Reload() in the interactive shell and then call g(), it works. However this is not a preferred approach because I have to declare global names.

UPDATE 2:

It seems that the problem is independent of ipython. With the first version of routines.py if I start the python shell, and type the following by hand:

def Reload():
  execfile('routines.py', {}, globals())

g()

The call to g() also fails. But the following works:

execfile('routines.py')
g()

Solution

  • As @Bakuriu said, importing is much preferred. Ignoring that, what you want is

    def Reload():
        execfile('routines.py', globals())
    

    Lets clarify your example to show why it does not work.

    # Setup the namespace to use for execfile
    global_dict = {}
    local_dict = globals()
    execfile('routines.py', global_dict, local_dict)
    g() # raises NameError
    

    Since you are passing two different dicts to execfile, the file is executed as if it were in a class definition (from the docs). This means your functions are defined in local_dict but not global_dict.

    When you then call g(), it is executed using globals global_dict and a fresh empty local dict. Since neither global_dict or the new locals doesn't contain f we get a name error. By instead calling execfile('routines.py', globals()), we are using global_dict = globals() and local_dict = globals() so f is defined in g's globals.

    EDIT:

    You noticed that local_dict has both f and g, but global_dict does not in the second example. Defining any variable without explicitly marking it global will always make a local variable, this applies to modules too! It just so happens that normally a module has locals() == globals(); however, we broke this standard by using different local and global dicts. This is what I meant when I said "the file is executed as if it were in a class definition".