如何将脚本分成较小的脚本和模块并连接到工作区变量?

How to divide a script in a smaller script and a module and connect to workspace variable?

我使用 Python 在 Jupyter 中启动会话,并使用为我使用的应用程序部分定制的脚本。该脚本包含依赖于应用程序的字典和函数,但有些函数是通用的。我想制作一个通用模块并使启动脚本仅包含应用程序部分。困难在于我希望通用模块中的函数默认有应用程序字典。那么如何将这样的工作区字典连接到导入的函数呢?

下面一个非常简单的例子说明了这个问题。首先您会看到原始的总启动脚本。此代码有效。

import numpy as np
import matplotlib.pyplot as plt

parDict = {}
parDict['Y'] = 0.5
parDict['qSmax'] = 1.0
parDict['Ks'] = 0.1

def par(parDict=parDict, *x, **x_kwarg):
   """ Set parameter values if available in the predefined dictionaryt parDict. """
   x_kwarg.update(*x)
   x_temp = {}
   for key in x_kwarg.keys():
      if key in parDict.keys():
         x_temp.update({key: x_kwarg[key]})
      else:
         print(key, 'seems not an accessible parameter')
   parDict.update(x_temp)

而且我可以在笔记本中给出类似 par(Y=0.4) 的命令,然后检查字典 parDict 中的结果。

第二个(下图)您看到尝试将通用函数分解为一个模块,并且该函数被导入到启动脚本中。在实际模块下方。此代码不起作用。错误信息是:name 'parDict' is not defined 如何解决?

parDict = {}
parDict['Y'] = 0.5
parDict['qSmax'] = 1.0
parDict['Ks'] = 0.1

from test_module import par

和test_module.py

def par(parDict=parDict, *x, **x_kwarg):
   """ Set parameter values if available in the predefined dictionaryt parDict. """
   x_kwarg.update(*x)
   x_temp = {}
   for key in x_kwarg.keys():
      if key in parDict.keys():
         x_temp.update({key: x_kwarg[key]})
      else:
         print(key, 'seems not an accessible parameter')
   parDict.update(x_temp)

如果我在函数中去掉默认参数 parDict 然后它就可以工作,但是我必须有一个更长的调用,比如 par(parDict, Y=0.4)。我想避免这个冗长的调用并自动提供默认的 parDict。一个想法是在启动脚本中从导入的函数中创建一个新函数,并在此处连接到字典。但这似乎是一种笨拙的方法,还是唯一的选择?

在您尝试定义函数 par 的地方,字典 parDict 未定义。您可以将 parDict 的定义移动到 test_module.py 中(在函数定义之前)。

作为旁注,请注意 Python 中可变默认参数的危险: https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments

它们可能会导致意外行为。

我的建议:

def par(parDict=None, *x, **x_kwarg):
   """ Set parameter values if available in the predefined dictionaryt parDict. """

    if parDict is None:
        parDict = {}
        parDict['Y'] = 0.5
        parDict['qSmax'] = 1.0
        parDict['Ks'] = 0.1

    x_kwarg.update(*x)
    x_temp = {}
    for key in x_kwarg.keys():
       if key in parDict.keys():
          x_temp.update({key: x_kwarg[key]})
       else:
          print(key, 'seems not an accessible parameter')
    parDict.update(x_temp)

    return parDict 

然后这样调用:

parDict = par(Y=2)

我上面草拟的暂定解决方案的细节如下所示。关键思想是在 start-up 脚本中从模块导入函数 parX() ,然后创建一个新函数 par() ,其中 parDict 是默认参数,因此“连接”到这个级别的函数,而在模块级别,进行此连接不起作用。因此,在 start-up 脚本中,字典 parDict 隐含地是一个全局变量,函数 par() 可以对其进行操作。

parDict = {}
parDict['Y'] = 0.5
parDict['qSmax'] = 1.0
parDict['Ks'] = 0.1

from test_module2 import parX

def par(*x, **x_kwarg):
   parX(parDict, *x, **x_kwarg)

调整后test_module2.py为:

def parX(parDict, *x, **x_kwarg):
   """ Set parameter values if available in the predefined dictionary parDict. """
   x_kwarg.update(*x)
   x_temp = {}
   for key in x_kwarg.keys():
       if key in parDict.keys():
          x_temp.update({key: x_kwarg[key]})
       else:
          print(key, 'seems not an accessible parameter')
   parDict.update(x_temp)

第一次测试表明它有效。通过这种方式,我避免将 parDict 作为默认参数引入,而只是将其作为全局变量访问。因此,避免了在上面 link 中讨论的默认参数何时被评估的不确定性。这段代码有什么问题吗?

关于更好的方法的建议?

一般来说,我想避免使用全局变量并引入它们作为默认参数来提高可读性,但它可能会引入问题,正如上面 link 所解释的那样,我放弃了这个想法。我想使用像 parDict 这样的工作区字典作为全局变量的原因是具有非常短的 command-line 函数,例如:par(),... 来管理字典并稍后对其内容进行操作。工作区字典的名称始终相同,但内容取决于应用程序。在字典上运行的 par() 等一般函数需要逐渐更新和扩展等,并且最好从一个模块集中完成,而不是从每个单独的 start-up 脚本。

现在我整理了一个更好的解决方案,想与您分享!关键是利用 Python class-concept 可以将字典与相关功能结合起来,然后从模块中导入 class。在 setup-file 中,您在导入 class 之后用应用程序参数名称和值填充字典,并为关联的函数创建一个短名称。如果您愿意,可以用“抽象数据类型”来描述。

start-up 脚本现在看起来像这样:

from test_module3 import ParDict

p = {}
p['Y'] = 0.5
p['qSmax'] = 1.0
p['Ks'] = 0.1

parDict = ParDict('Test3', p)

par = parDict.parX

更新后的 test_module3 现在稍微复杂一些:

class ParDict(object):
   """"Tool to associate a dictionary parDict with robustified way to   
       modifiy parameter values."""
          
   def __str__(self):
      return self.id

   def __init__(self, id, parDict):
      self.id = id 
      self.parDict = parDict
  
   def parX(self, *x, **x_kwarg):
      x_kwarg.update(*x)
      x_temp = {}
      for key in x_kwarg.keys():
         if key in self.parDict.keys():
            x_temp.update({key: x_kwarg[key]})
         else:
            print(key, 'seems not an accessible parameter')
      self.parDict.update(x_temp)
  

终于在 Jupyter Notebook 中推出了,您现在可以像以前一样使用简化的 command-line par(Y=0.4, Ks=0.08) 等更改 parDict 中的参数值