如何使用Fortran 90模块数据

41

假设你有一个Fortran 90模块,其中包含大量的变量、函数和子程序。在你的USE语句中,你遵循哪种惯例:

  1. 使用, only :语法明确声明你正在使用哪些变量/函数/子程序,例如:USE [module_name], only : variable1, variable2, ...
  2. 插入一条全局的USE [module_name]语句?

一方面,only子句使得代码有点冗长。然而,在代码中它强制你重复自己,如果你的模块包含大量的变量/函数/子程序,代码就会变得混乱。

这里是一个例子:

module constants
  implicit none
  real, parameter :: PI=3.14
  real, parameter :: E=2.71828183
  integer, parameter :: answer=42
  real, parameter :: earthRadiusMeters=6.38e6
end module constants

program test
! Option #1:  blanket "use constants"
!  use constants
! Option #2:  Specify EACH variable you wish to use.
  use constants, only : PI,E,answer,earthRadiusMeters
  implicit none

  write(6,*) "Hello world.  Here are some constants:"
  write(6,*) PI, &
       E, &
       answer, &
       earthRadiusInMeters
end program test

更新

希望有人像“Fortran?只需要用C#重写它!”一样说话,这样我就可以给你点个踩。


更新

我喜欢Tim Whitcomb的答案,其中比较了Fortran的 USE modulename 与Python的 from modulename import * 。 这是Stack Overflow以前讨论过的一个主题:

  • ‘import module’ or ‘from module import’

    • 在一个答案中,Mark Roddy提到:

      不要使用'from module import *'。 对于任何合理大型的代码集,如果您'import *',则可能会将其固定到模块中,无法删除。 这是因为很难确定代码中使用的项目来自'module',使得很容易达到您认为不再使用导入,但极其难以确定的程度。

  • Python导入的良好规则是什么?

    • dbr的答案包含

      不要使用from x import *-这使得您的代码非常难以理解,因为您无法轻松地看到方法来自哪里(来自x import *;来自y import *; my_func()在哪里定义?)。

因此,我倾向于通过显式列出我在模块中使用的所有项来达成一致。

USE modulename, only : var1, var2, ...

正如Stefano Borini提到的

如果你有一个模块太大,以至于你感到需要加上ONLY,这意味着你的模块太大了。将其拆分。


3
Fortran存在的问题之一是,当你从模块中导入内容时,会将所有东西都丢到全局命名空间中,就像JavaScript一样。在Python中,你可以使用"from foo.bar import *",也可以使用"from foo import bar"。但在Fortran中,你没有选择余地。每次使用USE时,就相当于执行了一个import *操作,将所有内容包括在全局命名空间中。这是Fortran 9X中最糟糕的问题之一。 - Stefano Borini
@StefanoBorini:最后一句话是否意味着这个问题在更近期的Fortran版本中得到了解决? - naught101
7个回答

28

我过去只是执行use 模块名命令 - 随着我的应用程序越来越大,我发现越来越难以找到函数的源代码(无需使用grep) - 办公室里其他一些浮动的代码仍然使用每个文件一个子程序的方式编写,这种方式有其自身的问题,但它使得使用文本编辑器穿越代码并快速定位所需内容变得更加容易。

通过这种经历,我已经成为在可能的情况下使用use...only命令的信奉者。我还开始学习Python,并将其视为from 模块名 import *的相同方式。模块提供了许多优秀的功能,但我更喜欢严格控制全局名称空间。


我喜欢你对导入Python模块的比喻 - 很棒的想法! - Pete
1
没错。我只使用显式的导入 "use ... only",就像在 Python 中我只使用 "from ... import" 一样。我认为这是正确的方法。 - Ondřej Čertík

18

平衡问题。

如果你只从模块中使用了少量的东西,那么添加“ONLY”就有意义,以清楚地指定你正在使用什么。

如果你从模块中使用了很多东西,指定“ONLY”后会跟着很多东西,那么它就没什么意义了。实际上,你只是在挑选你要用的部分,但事实上你对整个模块有所依赖。

然而,最好的哲学观念是这样的:如果你担心命名空间污染,并且你的模块非常大,以至于你感到需要添加“ONLY”,那么这意味着你的模块太大了,应该拆分成不同的模块。

更新:Fortran?用Python重写吧 ;)


6
这是一个有用的启发式方法,用于“我的模块应该有多大?”的问题。 - Tim Whitcomb

6
不是直接回答问题,而是提供另一个解决方案,我发现在某些情况下非常有用,如果出于某种原因您不想拆分模块并开始出现命名空间冲突。您可以使用派生类型将多个命名空间存储在一个模块中。
如果变量有一些逻辑分组,您可以为每个组创建自己的派生类型,在模块中存储此类型的实例,然后您可以仅导入需要的组。
小例子:我们有很多数据,其中一些是用户输入,另一些是杂项初始化的结果。
module basicdata
   implicit none
   ! First the data types...
   type input_data
      integer :: a, b
   end type input_data
   type init_data
      integer :: b, c
   end type init_data

   ! ... then declare the data
   type(input_data) :: input
   type(init_data) :: init
end module basicdata

现在,如果一个子程序只使用来自init的数据,你只需要导入它:
subroutine doesstuff
   use basicdata, only : init
   ...
   q = init%b
end subroutine doesstuff

这绝对不是一种普遍适用的解决方案,派生类型语法会增加额外的冗长性,当然,如果您的模块不是上面所述的basicdata类型,而是更多的allthestuffivebeenmeaningtosortout类型,那么这种方法几乎没有帮助。无论如何,我在以这种方式编写代码时有一些好运。


4

对于我来说,使用USE的主要优势在于它避免了将不需要的东西污染我的全局命名空间。


1
Agreed with most answers previously given, use ..., only: ... is the way to go, use types when it makes sense, apply python thinking as much as possible. Another suggestion is to use appropriate naming conventions in your imported module, along with private/public statements.
For instance, the netcdf library uses nf90_<some name>, which limits the namespace pollution on the importer side.
use netcdf  ! imported names are prefixed with "nf90_"

nf90_open(...)
nf90_create(...)
nf90_get_var(...)
nf90_close(...)

同样地,这个库的ncio包装器使用nc_<some name>nc_readnc_write...)。重要的是,在这种设计中,use: ..., only: ...变得不那么相关,你最好通过在头部设置适当的private/public属性来控制导入模块的命名空间,这样读者只需快速查看就足以评估他们所面临的“污染”级别。这基本上与use ..., only: ...相同,但在导入的模块侧 - 因此只需编写一次,而不是在每次导入时编写。

还有一件事:就面向对象和Python而言,我认为区别在于Fortran并不真正鼓励类型绑定过程,部分原因是它是一个相对较新的标准(例如与许多工具不兼容,而且不太合理,因为它很不寻常),并且它会破坏方便的行为,如无程序派生类型副本(type(mytype) :: t1, t2t2 = t1)。这意味着你经常需要导入类型和所有可能的类型绑定过程,而不仅仅是类。这就使得Fortran代码与Python相比更加冗长,实用的解决方案,如前缀命名约定,可能会派上用场。

在我看来,最重要的是:选择编码风格时要考虑到将阅读它的人(包括你以后的自己),正如Python所教授的那样。最好的方式是每次导入都使用更冗长的use ..., only: ...,但在某些情况下,简单的命名约定就足够了(如果你足够有纪律...)。


1

是的,请使用use module, only: ...。对于具有多个程序员的大型代码库,这使得每个人都更容易理解代码(或者只需使用grep)。

请不要使用include,而是使用一个更小的模块代替。Include是源代码的文本插入,编译器不能像使用模块那样在同一级别上检查它,参见:FORTRAN: Difference between INCLUDE and modulesInclude通常会使人和计算机难以使用代码,因此不应使用。例如来自mpi-forum的内容:“强烈不建议使用mpif.h包含文件,并且在MPI的未来版本中可能会被弃用。”(http://mpi-forum.org/docs/mpi-3.1/mpi31-report/node411.htm)。


不要将第一段中的句子添加到第二段中。你的回答要么适合作为独立回答,要么就不适合,那就不要发表。先获得声望再发布评论,这很容易。像这样的句子只会引导人们投票删除你的问题。对于此类回答/评论有一个特殊的删除原因。 - Vladimir F Героям слава
@VladimirF,这是一个很好的想法和删除的有效原因。我偶然发现了这个问题,并且假设我会忘记,所以我决定发布这篇文章,因为我认为使用include是如此糟糕的实践,应该被弃用,至少应该积极地反对。它通常使人类和计算机都更难处理,因此不应该使用。例如mpi-forum中的例子:“强烈不建议使用mpif.h包含文件,并且在MPI的未来版本中可能会被弃用。”(https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node411.htm)。因此,我给出了一个有效的答案并添加了一条备注。编辑过的帖子。 - Knut Gjerden
我同意。你可以将这个引用放入答案中。不幸的是,OpenMPI手册仍然在每个子程序手册条目之前只显示include mpif.h,至少在1.8版本中是这样。 - Vladimir F Героям слава

0

我知道我来晚了一点,但如果你只需要一组常量而不是计算出的值,你可以像C语言一样创建一个包含文件:

在一个文件中,例如constants.for

real, parameter :: pi = 3.14
real, parameter :: g = 6.67384e-11
...


program main
    use module1, only : func1, subroutine1, func2 
    implicit none

    include 'constants.for'
    ...
end program main

编辑以删除"real(4)",因为有些人认为这是不好的实践。


请不要教人们在原问题不需要时使用 real(4) 这种不好的习惯。此外,我不能同意包含方法更好或更简单的说法。 - Vladimir F Героям слава
real(4)只是我习惯性的复制粘贴。我考虑了你的另一个评论,但我还是把它留了下来。人们可以用无数种方式完成同样的事情。重要的是人们要尝试并找到自己喜欢的方法。在我看来,“include”文件只有在他/她没有使用Makefile并且没有添加“-I.”时才会更加困难。 - Forrest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接