问题 在Python脚本中定义全局函数


我是Python的新手。我正在编写一个脚本,它将使用Runge-Kutta方法在数值上整合一组常微分方程。由于Runge-Kutta方法是一种有用的数学算法,我将它放在自己的.py文件中,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

该方法需要知道用户正在使用的方程组以执行算法,因此它调用函数 diff(x) 这将找到rk4它需要工作的衍生物。由于方程式会随着使用而改变,我希望在运行特定问题的脚本中定义diff()。在这种情况下,问题是汞的轨道,所以我写道 mercury.py。 (这不是它最终会看起来如何,但为了弄清楚我在做什么,我已经简化了它。)

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)

当我运行mercury.py时,我收到一个错误:

  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

我接受了 diff() 不是全局函数,当rk4运行时,它对diff一无所知。显然rk4是一小段代码,我可以把它推到我当时正在使用的任何脚本中,但我认为Runge-Kutta积分器是一个基本的数学工具,就像在NumPy中定义的数组一样,所以它有意义的是使它成为一个被调用的函数,而不是在每个使用它的脚本中定义的函数(可能很多)。但我也不能告诉rk4.py从特定的.py文件导入特定的差异,因为这首先破坏了我想要的rk4的普遍性。

有没有办法在像mercury.py这样的脚本中全局定义diff,这样当调用rk4时,它会知道diff?


5781
2017-10-02 14:11


起源

请修改你的缩进。 - Lukas Graf
顺便说一下,块引号中的空格对我的代码来说并不准确,但是我不知道如何在没有额外空格的情况下获取块引号来生成新行。 - Dylan B
使用 { } 问题编辑器中的图标用于标记大块源代码。 - Lukas Graf
另一个提示,如果我可以: PEP8,Python代码的官方风格指南,建议总是在运算符周围放置空格(比如 +, /等你的情况)。这使代码更易读。 (对于这个问题,它很好,但在你自己的代码中我会改变它)。 - Lukas Graf
顺便说一句,这是第一个问题的一个地狱:简洁的标题,“这就是我想要做的”,“这就是我所尝试的”,“这就是发生的事情”,包括(相关的)代码,导入和回溯。太好了! :) - Lukas Graf


答案:


接受函数作为参数:

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

然后,当你打电话 rk4,只需传入要执行的函数:

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)

这可能是一个好主意 mercury 接受 diff 作为一个参数,而不是从闭包(周围的代码)得到它。然后你必须照常传递它 - 你的电话 mercury 在最后一行会读 mercury(diff, 1, 1, 2)

函数是Python中的“一等公民”(几乎所有东西,包括类和模块),在某种意义上它们可以用作参数,保存在列表中,分配给名称空间中的名称等等。


11
2017-10-02 14:17



@JoranBeasley“不是非常Pythonic”?没门!这是 只要 Pythonic解决问题的方法。标准库绝对充满了接受和返回其他功能的功能,以及 @decorator 如果这不是一个主要的语言功能,语法将毫无意义。 - Benjamin Hodgson♦
关于那个什么不是pythonic?利用函数作为第一类对象是 非常 Python的。 - Lukas Graf
谢谢!这类似于我在IDL这样的语言中看到它有本机rk4例程,并且似乎让我保留了我喜欢的结构。 - Dylan B
@JoranBeasley OP说“我也不能告诉rk4.py从特定的.py文件导入特定的差异,因为这首先破坏了我想要的rk4的普遍性。”他想要每次打电话 rk4 能够定义自己的 diff。唯一的方法是将其作为参数传递。 from...import 打破可重用性。 - Benjamin Hodgson♦
@JoranBeasley不要对传递函数感到娇气。这是一个非常自然和Pythonic的事情。正如我所说,标准库有 负载 函数 - 接受函数。看看吧 functools 模块。 - Benjamin Hodgson♦


diff 已经是该模块的全球性 mercury.py。但为了使用它 rk4.py 你需要像这样导入它:

from mercury import diff

这是你问题的直接答案。

但是,路过 diff 功能 rk4 正如@poorsod所建议的要优雅得多,也避免了它之间的循环依赖 mercury.py 和 rk4.py所以我建议你这样做。


3
2017-10-02 14:22



我认为这是更优雅的解决方案...传递函数对我来说似乎是一种危险的做法...(授予此解决方案需要代码重构以避免循环依赖) - Joran Beasley
@JoranBeasley是的,从那以后 diff 仅用于 rk4.py 重构代码以将它放在那里可能是有意义的。但是,特别是对于diff函数,传递函数对象的模式非常自然。老实说,我不明白这是多么危险。 - Lukas Graf
是的,我可以看到它的论点...它对我来说似乎仍然是粗略的:P ...我已经在我的代码中完成了这样的事情(传递函数)并且99%的时间我总是传递相同的函数... (除了像sum / max / sorted / etc这样的内置函数...) - Joran Beasley
在这种情况下,我确信不会是这种情况,因为我将使用不同的 diff 在完成这项任务的过程中,在我过去的研究中(我是一名物理系学生),我已经多次申请 rk4 方法适用于具有不同应用的不同系统,通常使用自己独特的定义 diff。 - Dylan B
@DylanB你也在你的问题中说过 但我也不能告诉rk4.py从特定的.py文件中导入特定的diff,我第一次错过了。所以通过diff函数绝对是走到这里的正确方法。好问题BTW! - Lukas Graf