Fortran的Namelist
Published:
基于 Namelist 的 Fortran 配置读写
Namelist 是 Fortran 标准提供的免格式(format-free)键值输入输出机制:把一组变量绑定到一个组名,再用 READ/WRITE 的 NML= 说明符整组读写。该能力最早由部分 FORTRAN 77 编译器扩展引入,自 Fortran 90 起进入标准,并沿用至 Fortran 2018。科学计算程序(数值天气预报、气候模式、轨道与 GNSS 处理等)普遍用 .nml 文件作为运行时配置,从而避免为每个参数手写格式串。
依据 ISO/IEC Fortran 2018 第 8.9 节与第 13.11 节,以及 Intel Fortran NAMELIST 说明、Programming in Modern Fortran — Namelists,下文给出可直接编译运行的写法与常见陷阱。
1. 为何使用 Namelist
与固定格式 READ(10,'(2I5)') 或手写解析器相比,Namelist 的工程价值体现在三点。
第一,按名赋值:输入记录不必覆盖组内全部变量,未出现的变量保持原值;赋值顺序与 NAMELIST 声明顺序无关。第二,人机可读:配置文件是纯文本,可用编辑器直接改参数后重跑,无需重编译。第三,类型覆盖面广:标量、数组、字符、逻辑、复型以及派生类型分量均可进入同一组。
适用边界同样明确:Namelist 面向结构化参数表,不适合替代二进制大规模场数据,也不适合需要严格模式校验的复杂 schema(此类场景可另用 JSON/YAML 外挂解析,或与 Namelist 分层配合)。
2. 声明语法
标准形式为:
NAMELIST /namelist-group-name/ variable-name-list
同一作用域内,同一组名可多次出现,后继列表视为对该组的续写;同一变量也可出现在多个组中。约束包括:组内对象不得为假定大小数组(assumed-size);组名不得经 USE 关联引入;对象须在本作用域显式/隐式定型,或经 host/USE 关联可见。
integer :: a, b
real :: x
namelist /ab/ a, b
namelist /ab/ x ! 等价于向组 ab 追加 x
亦可在一条语句中定义多组:
namelist /input/ name, grade, date /output/ total, name
3. 外部文件格式
每个 Namelist 记录以 &组名 起始,以单独的 / 结束;中间为 名字=值 序列。名字大小写不敏感(FOO 与 foo 指向同一对象)。同一文件可存放多组;多数编译器允许在记录中使用 Fortran 注释风格内容,但可移植写法宜保持记录体干净。
&ab
a = 1,
b = 2
/
数组与派生类型用下标或 % 分量名指定:
&EXAMPLE
X = 100
Y = 500
R(1) = 10.5
R(2) = 20.25
PERSON%ID = 1
PERSON%NAME = 'Alice'
PERSON%AGE = 27
/
逻辑值写作 .TRUE. / .FALSE.(或编译器接受的缩写形式,如 T/F,以所用编译器文档为准)。字符串用单引号或双引号包围。
4. 最小完整示例
原稿中 /namelist/ab/a,b/ 与输入文件首行 /& 均不符合标准。标准声明为 namelist /ab/ a, b;输入必须以 &ab 开头。下面给出与原稿意图一致、按 Fortran 2018 风格改写的可运行版本。
程序 ab_demo.f90:
program ab_demo
use, intrinsic :: iso_fortran_env, only: output_unit, error_unit
implicit none
integer :: a, b
integer :: fu, ios
namelist /ab/ a, b
a = 0
b = 0
open (newunit=fu, file='ab.namelist', status='old', action='read', iostat=ios)
if (ios /= 0) then
write (error_unit, '(a)') '无法打开 ab.namelist'
stop 1
end if
read (fu, nml=ab, iostat=ios)
if (ios /= 0) then
write (error_unit, '(a)') 'Namelist 读取失败:请检查组名与赋值语法'
close (fu)
stop 2
end if
close (fu)
write (output_unit, fmt='(2(1x,i0))') a, b
! 也可回写标准输出核对:
! write (output_unit, nml=ab)
end program ab_demo
配置文件 ab.namelist:
&ab
a = 1,
b = 2
/
编译与运行:
gfortran -std=f2018 -o ab_demo ab_demo.f90
./ab_demo
# 输出: 1 2
读写控制流可概括为:
flowchart LR
A[声明变量] --> B["namelist /组名/ 变量列表"]
B --> C[open 配置文件]
C --> D["read/write(..., nml=组名)"]
D --> E[按 iostat 判定成败]
E --> F[close]
5. 读写 API 要点
READ / WRITE 使用 NML= 指定组名;可与 UNIT=、IOSTAT=、IOMSG=、ERR=、END= 等说明符组合。当省略 NML= 关键字时,单元号须为第一说明符、组名须为第二说明符,且不得再写格式说明符。
read (unit=fu, nml=ab, iostat=ios, iomsg=msg)
write (unit=fu, nml=ab)
输出顺序与 NAMELIST 中对象声明顺序一致;输入顺序任意。组内变量若未在输入中出现,则保持调用前的值——因此应在 READ 之前赋予合理缺省值,作为“可选配置项”的基线。
DELIM= 说明符可控制字符输出的引号风格(如 DELIM='QUOTE'),便于生成可再被 Namelist 读回的文本。
6. 数组、派生类型与多组文件
6.1 数组
可对整段赋值,也可对单个元素或片段赋值。Intel 文档示例中,连续元素可写为:
&example
iarray(8) = 41, 42, 43
/
表示自下标 8 起依次填入后续常量。
6.2 派生类型
将类型变量本身列入 Namelist 后,输入侧用 变量%分量 寻址。推荐在 READ 前用构造函数设定缺省,读取失败时仍可回退到缺省人:
type :: person_type
integer :: id
character(len=32) :: name
integer :: age
end type
type(person_type) :: person
integer :: x, y
real :: r(2)
namelist /EXAMPLE/ x, y, r, person
person = person_type(-1, 'Jane Doe', 0)
x = 0; y = 0
r = [0.0, 0.0]
6.3 单文件多组
同一文件可串联多段 &组名 ... /。程序按组名分别 READ:先读物理参数组,再读数值积分组,互不干扰。组名在输入记录中必须与源码中的 Namelist 组名一致。
7. 稳健封装模式
生产代码宜把打开、读取、关闭与错误信息集中到子程序,并在失败时保留缺省值:
subroutine read_ab_namelist(path, a, b, ok)
character(len=*), intent(in) :: path
integer, intent(inout) :: a, b
logical, intent(out) :: ok
integer :: fu, ios
namelist /ab/ a, b
ok = .false.
open (newunit=fu, file=path, status='old', action='read', iostat=ios)
if (ios /= 0) return
read (fu, nml=ab, iostat=ios)
close (fu)
ok = (ios == 0)
end subroutine read_ab_namelist
跨语言维护配置时,可用 Python 侧的 f90nml 解析与改写 .nml,再交 Fortran 运行时读取,避免两边各维护一份手工格式约定。
8. 与其它配置方式的对照
| 方式 | 优点 | 局限 |
|---|---|---|
| Namelist | 标准内建、按名可选赋值、科学计算生态成熟 | schema 弱;扩展语法因编译器而异 |
格式化 READ | 紧凑、确定性高 | 改字段需同步改格式与文档 |
| 命令行参数 | 适合少量开关 | 长参数表难维护 |
| 外挂 JSON/YAML | 工具链丰富、校验容易 | 需额外库或自写解析 |
气候与地球系统模式(如 CESM 及相关分量)广泛依赖 Namelist 作为组件级控制面:改物理开关、时间步、输出频率往往只需编辑 .nml,不必触碰源码。
9. 常见错误与修正
原稿示例与实践中的高频问题可对照下表排查。
| 现象 | 原因 | 修正 |
|---|---|---|
编译失败:/namelist/.../ | 非标准声明写法 | 使用 namelist /组名/ 变量列表 |
| 运行时读失败 | 文件以 /& 或缺少组名开头 | 使用 &组名 起始、/ 结束 |
| 部分变量仍为 0 | 输入未给出该名,且读前未设缺省 | 读前赋缺省;或补全输入项 |
| 字符截断或错位 | 长度声明不足 / 引号不配对 | 加长 character(len=...);检查引号 |
| 假定大小数组入组 | 标准禁止 | 改为显式形状或可分配数组后再入组 |
| 回退读失败 | 对 list-directed/namelist 记录做 BACKSPACE | 标准禁止对该类记录回退 |
另需注意:多数环境中 Namelist 输入名大小写不敏感,但字符值本身区分大小写;逻辑与复型字面量须符合 Fortran 词法,勿混用其它语言的 true/false。
10. 小结
Namelist 把“变量集合”提升为一等配置对象:源码侧用 NAMELIST 声明组,文件侧用 &组名 ... / 提供键值,I/O 侧用 NML= 一次完成读写。正确写法是标准声明加标准记录头,并在读取前设置缺省、用 IOSTAT 判定失败。掌握数组下标、派生类型 % 分量与多组文件后,即可覆盖数值程序中绝大多数运行时参数管理需求。
参考文献
- ISO/IEC JTC1/SC22/WG5. Fortran 2018 Final Draft International Standard(N2155),§8.9 NAMELIST statement,§13.11 Namelist formatting. https://wg5-fortran.org/N2151-N2200/ISO-IECJTC1-SC22-WG5_N2155_Fortran_2018_Final_Draft_International_Standard.pdf
- Intel Corporation. NAMELIST — Intel® Fortran Compiler Developer Guide and Reference. https://www.intel.com/content/www/us/en/docs/fortran-compiler/developer-guide-reference/2025-1/namelist.html
- Programming in Modern Fortran. Namelists. https://cyber.dabamos.de/programming/modernfortran/namelists.html
- Oracle. NAMELIST I/O (FORTRAN 77 Language Reference). https://docs.oracle.com/cd/E19957-01/805-4939/6j4m0vnc6/index.html
- Ward, M. f90nml — Python namelist parser for Fortran. https://github.com/marshallward/f90nml
