Defining a global function in a Python script

36,079

Solution 1

Accept the function as an argument:

def rk4(diff,  # accept an argument of the function to call
        x, dt)
    k1=diff(x)*dt
    k2=diff(x+k1/2)*dt
    k3=diff(x+k2/2)*dt
    k4=diff(x+k3)*dt
    return x+(k1+2*k2+2*k3+k4)/6

Then, when you call rk4, simply pass in the function to be executed:

from rk4 import rk4
import numpy as np

def diff(x):
    return x

def mercury(u0,phi0,dphi):
    x=np.array([u0,phi0])
    dt=2
    x=rk4(diff,  # here we send the function to rk4
          x, dt)
    return x
mercury(1,1,2)

It might be a good idea for mercury to accept diff as an argument too, rather than getting it from the closure (the surrounding code). You then have to pass it in as usual - your call to mercury in the last line would read mercury(diff, 1, 1, 2).

Functions are 'first-class citizens' in Python (as is nearly everything, including classes and modules), in the sense that they can be used as arguments, be held in lists, be assigned to names in namespaces, etc etc.

Solution 2

diff is already a global in the module mercury.py. But in order to use it in rk4.py you would need to import it like this:

from mercury import diff

That's the direct answer to your question.

However, passing the diff function to rk4 as suggested by @poorsod is much more elegant and also avoids a circular dependency between mercury.py and rk4.py, so I suggest you do it that way.

Share:
36,079
Dylan B
Author by

Dylan B

Updated on July 20, 2022

Comments

  • Dylan B
    Dylan B almost 2 years

    I'm new to Python. I am writing a script that will numerically integrate a set of ordinary differential equations using a Runge-Kutta method. Since the Runge-Kutta method is a useful mathematical algorithm, I've put it in its own .py file, rk4.py.

    def rk4(x,dt):
        k1=diff(x)*dt
        k2=diff(x+k1/2)*dt
        k3=diff(x+k2/2)*dt
        k4=diff(x+k3)*dt
        return x+(k1+2*k2+2*k3+k4)/6
    

    The method needs to know the set of equations that the user is working with in order to perform the algorithm, so it calls a function diff(x) that will find give rk4 the derivatives it needs to work. Since the equations will change by use, I want diff() to be defined in the script that will run the particular problem. In this case the problem is the orbit of mercury, so I wrote mercury.py. (This isn't how it will look in the end, but I've simplified it for the sake of figuring out what I'm doing.)

    from rk4 import rk4
    import numpy as np
    
    def diff(x):
        return x
    
    def mercury(u0,phi0,dphi):
        x=np.array([u0,phi0])
        dt=2
        x=rk4(x,dt)
        return x
    
    mercury(1,1,2)
    

    When I run mercury.py, I get an error:

      File "PATH/mercury.py", line 10, in mercury
        x=rk4(x,dt)
      File "PATH/rk4.py", line 2, in rk4
        k1=diff(x)*dt
    NameError: global name 'diff' is not defined
    

    I take it since diff() is not a global function, when rk4 runs it knows nothing about diff. Obviously rk4 is a small piece of code and I could just shove it into whatever script I'm using at the time, but I think a Runge-Kutta integrator is a fundamental mathematical tool, just like the array defined in NumPy, and so it makes sense to make it a function that is called rather one that is defined in every script that uses it (which may be many). But I also can't go telling rk4.py to import a particular diff from a particular .py file, because that ruins the generality of rk4 that I want in the first place.

    Is there a way to define diff globally within a script like mercury.py so that when rk4 is called, it will know about diff?

  • Benjamin Hodgson
    Benjamin Hodgson over 11 years
    @JoranBeasley "Not very Pythonic"? No way! This is the only Pythonic solution to the problem. The standard library is absolutely full of functions that accept and return other functions, and the @decorator syntax would make no sense if this weren't a major language feature.
  • Lukas Graf
    Lukas Graf over 11 years
    What's not pythonic about that? Making use of functions being first class objects is very pythonic.
  • Dylan B
    Dylan B over 11 years
    Thanks! This is similar to how I've seen it done in languages like IDL that have native rk4 routines, and seems to let me preserve the structure I'd like.
  • Joran Beasley
    Joran Beasley over 11 years
    OK ... I guess ... I still think just importing diff to rk4 is better and avoiding the circular dependancy by properly refactoring the code ... but yeah I didnt realize he was calling rk4 from in the mercury function ... sorry (Ill delete my earlier comment)
  • Joran Beasley
    Joran Beasley over 11 years
    I think this is the more elegant solution imho ... passing functions around seems like a dangerous practice to me... (granted this solution requires a code refactor to avoid the circular dependency)
  • Lukas Graf
    Lukas Graf over 11 years
    @JoranBeasley Yes, since diff is only used in rk4.py it would probably make sense to refactor the code to put it there. But still, especially for a diff-function the pattern of passing in function objects lends itself very naturally. And I honestly don't see how it could be dangerous.
  • Joran Beasley
    Joran Beasley over 11 years
    yes I can see the argument for it ... It just still seems sketchy to me :P ... Ive done stuff like this in my code(passing functions) and 99% of the time im passing always the same function...(with the exceptions being the builtins like sum/max/sorted/etc...)
  • Benjamin Hodgson
    Benjamin Hodgson over 11 years
    @JoranBeasley the OP says "I also can't go telling rk4.py to import a particular diff from a particular .py file, because that ruins the generality of rk4 that I want in the first place." He wants each call to rk4 to be able to define its own diff. The only way to do this is to pass it in as an argument. from...import breaks reusability.
  • Joran Beasley
    Joran Beasley over 11 years
    ahh ok I missed that stipulation ... then this makes perfect sense
  • Benjamin Hodgson
    Benjamin Hodgson over 11 years
    @JoranBeasley Don't be squeamish about passing functions around. It's a perfectly natural and Pythonic thing to do. As I said, the standard library has loads of functions-that-accept-functions. Take a look at the functools module.
  • Dylan B
    Dylan B over 11 years
    In this case I'm sure that won't be the case, as I'm going to be using different diff several times in the course of completing this assignment, and in my research in the past (I'm a physics student) I've had to apply this rk4 method to diverse systems with different applications, often using their own unique definitions of diff.
  • Lukas Graf
    Lukas Graf over 11 years
    @DylanB You also stated in your question But I also can't go telling rk4.py to import a particular diff from a particular .py file, I first missed that. So passing the diff-function is absolutely the right way to go here. Good question BTW!