首页 > 解决方案 > 在 python 中构建海洋建模代码

问题描述

我开始使用 python 进行数值模拟,特别是我从这个项目开始构建我的,这将比这更复杂,因为我将不得不尝试很多不同的方法和配置。我全职研究 Fortran90 代码和 Matlab 代码,这两种语言是我的“母语”。在这两种语言中,一个人可以根据需要自由地构建代码,我正在尝试模仿这个功能,因为在我的领域(计算海洋学)中事情变得相当复杂。以我每天使用的代码为例,NEMO(这里是主页,这里是源代码). (NEMO 的)源代码方便地划分在文件夹中,每个文件夹都包含用于特定任务的模块和方法(例如,域离散化例程在文件夹 DOM 中,垂直物理在文件夹 ZDF 中,横向物理在 LDF 和等等),这是因为所涉及的过程(物理或纯数学)是完全不同的。我正在尝试构建的是这个

剧本create_conf.py包含以下结构,它应该从终端获取一个字符串输入,创建一个具有该名称的文件夹并将 /src 文件夹的所有文件和子目录复制到里面,这样就可以把这个配置的所有输入文件和最终修改源代码以创建用于配置的临时源代码。这种源代码的重复在海洋建模社区中很常见,因为两种不同的配置(如地中海和里海)不仅在输入文件(如地形、海岸线等)方面可能不同,而且在建模本身方面也可能不同,这意味着您需要对每个配置的源代码进行的修改可能很大。

import os, sys
import shutil
def create_conf(conf_name="new_config"):
    cfg_dir = os.getcwd() + "/cfgs/"  
    # Check if configuration exists
    try:
        os.makedirs(cfg_dir + conf_name)
        print("Configuration " + conf_name + " correctly created")
    except FileExistsError:
        # directory already exists
        # Handles overwriting, duplicates or stop
    # make a copy of "/src" into the new folder
    return

# This is supposed to be used directly from the terminal
if __name__ == '__main__':
    filename = sys.argv[1]
    create_conf(filename)

该脚本swm_main.py可以被认为是对必要例程的调用列表,具体取决于您要考虑的过程类型,就像

import numpy as np
from DOM.swm_domain import set_grid
from swm_param import set_param, set_timestep, set_viscosity
# initialize dictionary (i.e. structure) containing all the parameters of the run 
global param
param = dict()
# define the parameters (i.e. call swm_param.py)
set_param(param)
# Create the grid
set_grid(param)

调用的两个例程只获取一个特定字段param并为其分配一个值,例如

import numpy as np
import os
def set_param(param):
    param['nx'] = 32               # number of grid points in x-direction
    param['ny'] = 32               # number of grid points in y-direction  
    return param

现在,主要讨论的话题是如何在python中实现这种结构。我几乎总能找到单一的源代码(同一文件中的所有例程)或同一文件夹中的一系列文件。我希望有一些更好的组织,但我发现浏览的解决方案用一个文件夹填充 /src 中的每个子文件夹__pycache__,我需要__init__.py在每个文件夹中放置一个文件。我不知道为什么,但是这两件事让我觉得这种方法有些草率。此外,我需要在每个文件中导入模块(如 numpy),我想知道这是否有效。

您认为保持这种结构并使其尽可能简单会更好吗?谢谢你的帮助

标签: python

解决方案


据我了解,这里的实际问题是:

我发现浏览的解决方案用一个文件夹填充 /src 中的每个子文件夹__pycache__,我需要__init__.py在每个文件夹中放置一个文件......这让我觉得这种方法有些草率。

将您的代码放入包中并没有什么草率或不合时宜的做法。为了能够从.py目录中的文件导入,必须满足以下两个条件之一:

  • 该目录必须在您的sys.path,
  • 该目录必须是一个包,并且该包必须是您的某个目录的子目录sys.path(或者一个包的子目录,它是您的某个目录的子目录sys.path

第一个解决方案在代码中通常是 hacky,尽管通常适用于测试,并且涉及修改sys.path以添加您想要的每个目录。这通常是 hacky,因为将代码放入包中的全部意义modeller在于包结构对源代码中的一些自然划分进行编码:例如,包在概念上与包不同quickgui,并且每个包都可以在不同的程序中相互独立使用.

将目录制作成包的最简单[1] 方法是在__init__.py其中放置一个。该文件应包含概念上属于包级别的任何内容,即不在模块中。将其留空可能是合适的,但从模块中导入公共函数/类/变量通常是个好主意,因此您可以这样做from mypkg import thing而不是from mypkg.module import thing. 包在概念上应该是完整的,这通常意味着您应该能够(理论上)从多个地方使用它们。有时你不想要一个单独的包:你只想要一个命名约定,比如gui_tools.py gui_constants.py, model_tools.py,model_constants.py等。该__pycache__文件夹只是 python 缓存字节码以使将来的导入更快:你可以移动它或阻止它,但只需添加*__pycache__*到你的.gitignore忘记他们。

最后,由于您来自非常不同的语言:

  • 很多由科学家(而不是程序员)编写的 Python 代码是非常不符合 Python 的恕我直言。十亿行长的单个 python 文件不是很好的风格[2]。Python 更喜欢可读性,总是:derived_model不叫dm1. 如果你这样做,你很可能会发现你不需要像你想象的那么多的目录。
  • 在每个文件中导入相同的模块是一个微不足道的成本:python 导入一次:每个其他导入只是绑定在sys.modules. 始终显式导入。
  • 一般来说,不要担心 python 的性能。尽可能清楚地编写代码,然后在需要时对其进行分析,并找出慢的地方。Python 是如此之高,以至于在编译语言中学习的微优化可能会适得其反。
  • 最后,这主要是个人的,不要用大写字母给文件夹/模块名称。FORTRAN 可能会鼓励这样做,它是在通常对文件名不区分大小写的机器上编写的但我们不再有这些限制。在 python 中,我们为常量保留大写字母,所以当我必须修改执行大写字母时,我觉得很奇怪。同样,“DOM”让我想到了文档对象模型,这可能不是您在这里的意思。

参考

[1] Python 确实有隐式命名空间包,但您最好还是使用显式包来表明您打算制作一个包(并避免各种导入问题)。

[2] 有关如何构建事物的更多约定,请参见pep8 。我还建议查看一些体面的通用库,看看它们是如何做事的:它们往往是由专注于编写干净、可维护的代码的主流程序员编写的,而不是由专注于解决高度具体(而且通常非常复杂)问题。


推荐阅读