Списочный ввод-вывод и namelist
Списочный I/O печатает и читает данные без явного формата, а namelist превращает группу переменных в самоописывающий конфигурационный блок.
Списочный (list-directed) ввод-вывод — режим
print */read *, где формат выбирает сама библиотека по типам значений. namelist — именованная группа переменных, читаемая и записываемая в самоописывающем форматеимя=значение.
Списочный вывод: быстро и небрежно
Звёздочка вместо строки формата — print * или write(*,*) — означает «печатай как сочтёшь нужным». Библиотека сама выбирает ширину и форму для каждого значения, разделяя их пробелами. Это идеально для отладки и черновых распечаток: не нужно подбирать дескрипторы, достаточно перечислить переменные.
program demo_list
implicit none
integer :: n = 7
real :: x = 2.5, y = -0.001
character(len=*), parameter :: name = "опыт"
print *, "n=", n, " x=", x
print *, "y=", y, " имя=", name
print *, "массив:", [1, 2, 3, 4]
end program demo_list
Вывод (точная ширина зависит от компилятора):
n= 7 x= 2.50000000 y= -1.00000005E-03 имя=опыт массив: 1 2 3 4
Платой за удобство является непредсказуемость: точное число пробелов и знаков зависит от компилятора, поэтому списочный вывод не годится для файлов, которые читают другие программы по фиксированным позициям, и для отчётов, где важна аккуратная вёрстка. Его ниша — диагностика и неформальный вывод. Заметьте также: ведущий пробел в начале каждой строки — наследие исторического «управления кареткой», его учитывают при сравнении вывода.
Списочный ввод: чтение свободных данных
Зеркальный режим read * разбирает входной поток по разделителям (пробелы, запятые, перевод строки), присваивая значения переменным по порядку. Это самый удобный способ читать данные, записанные «как есть», без жёсткой колоночной структуры.
integer :: k
real :: a, b
read *, k, a, b ! вход: "5 1.5, 2.0" → k=5, a=1.5, b=2.0
Списочный ввод понимает несколько приятных соглашений. Разделителем служат пробелы и/или запятые. Повторяющееся значение можно задать как 3*0.0 (три нуля). Звёздочка-«пропуск» r* пропускает позиции, оставляя переменные без изменения. Строку в кавычках читают как символьное значение. Эти правила делают списочный ввод гибким для научных входных файлов, набитых человеком.
namelist: самоописывающая конфигурация
Когда у программы десятки параметров, передавать их позиционным списком неудобно и хрупко: добавили параметр — сместились все позиции. namelist решает это, связывая значения с именами. Сначала объявляют группу: namelist /имя_группы/ список_переменных. Затем эту группу можно целиком записать или прочитать одним оператором, а во входном файле параметры задаются как имя=значение в любом порядке, и лишние можно опускать.
program demo_namelist
implicit none
integer :: max_iter = 100
real :: tol = 1.0e-6
logical :: verbose = .false.
character(len=20) :: method = "gauss"
namelist /config/ max_iter, tol, verbose, method
! записать текущие значения в namelist-формате:
write(*, nml=config)
end program demo_namelist
Вывод:
&CONFIG MAX_ITER= 100, TOL= 9.99999997E-07, VERBOSE= F, METHOD="gauss", /
Блок начинается с &CONFIG и заканчивается косой чертой /. Каждая строка — ИМЯ=значение. Этот же текст можно прочитать обратно. Соответствующий входной файл, например run.nml, выглядит так:
&config
max_iter = 500
tol = 1.0e-8
verbose = .true.
/
А программа читает его одним оператором:
integer :: u
open(newunit=u, file="run.nml", status="old", action="read")
read(u, nml=config)
close(u)
! теперь max_iter=500, tol=1e-8, verbose=.true., method остался "gauss"
Ключевые достоинства namelist: параметры именованы (порядок не важен), опущенные сохраняют значения по умолчанию, формат человекочитаем и редактируется в любом текстовом редакторе. Именно поэтому namelist десятилетиями служит стандартным механизмом конфигурации научных кодов — от моделей климата до решателей CFD: входной .nml-файл задаёт сотни параметров запуска, и его удобно править и хранить в системе контроля версий.
Как работает под капотом
Списочный вывод реализуется как форматный, где библиотека сама подбирает дескриптор под тип и значение: для целого выбирает достаточную ширину I, для вещественного — представление, гарантирующее обратную читаемость (часто близкое к полной точности типа). Поэтому списочный вывод вещественных нередко даёт «некрасивые» хвосты вроде 2.50000000 или -1.00000005E-03 — библиотека печатает достаточно цифр, чтобы значение можно было прочитать назад без потерь. namelist устроен как именованный словарь: при записи библиотека для каждой переменной печатает её имя, знак равенства и значение; при чтении — разбирает текст, ищет переменные группы по именам и присваивает. Неизвестное имя во входе обычно вызывает ошибку (зависит от компилятора), а отсутствующее — просто оставляет переменную нетронутой. Это и даёт устойчивость к добавлению параметров: старые входные файлы продолжают работать.
Тонкости списочного ввода: разделители и неполные строки
Списочный ввод обладает рядом полезных, но не всем известных соглашений, понимание которых избавляет от загадочных ошибок чтения. Во-первых, перевод строки эквивалентен разделителю: один оператор read *, a, b, c прочитает три значения, даже если они записаны на трёх отдельных строках входного файла — список ввода «жадно» забирает столько значений, сколько ему нужно, пересекая границы строк. Это удобно, но иногда неожиданно: лишнее значение на строке уйдёт в следующую переменную следующего read. Во-вторых, пустое значение (две запятые подряд 1,,3) означает «оставить эту переменную без изменения» — то есть пропуск, сохраняющий предыдущее или начальное значение. В-третьих, слэш во входе (/) досрочно завершает список ввода: оставшиеся переменные не считываются и сохраняют свои значения. Эти правила превращают списочный ввод в гибкий инструмент для рукописных файлов данных, но требуют дисциплины: смешав привычки разных языков, легко получить сдвиг значений.
Отдельно стоит сказать про строки в списочном вводе. Символьное значение, содержащее пробелы или запятые, обязательно заключают в апострофы или кавычки, иначе разделители «разрежут» его на части. Без кавычек строка читается до первого разделителя. Это частая ловушка при чтении, например, имён файлов или текстовых меток из данных: read *, name на входе my file.dat прочитает в name только my. Правильный вход — 'my file.dat'. Понимание этих мелочей отличает надёжный код чтения данных от хрупкого, ломающегося на «неудобных» входах.
namelist в практике научных кодов
Чтобы оценить, почему namelist десятилетиями остаётся стандартом конфигурации научного ПО, полезно взглянуть на реальный масштаб его применения. Большие модели — атмосферы, океана, плазмы, реакторов — имеют сотни управляющих параметров: шаги сетки, физические константы, флаги включения подмоделей, пути к данным, параметры численных схем. Передавать их аргументами командной строки невозможно, а зашивать в код — значит пересобирать программу при каждом изменении сценария. namelist решает это элегантно: все параметры собраны в один (или несколько) текстовых .nml-файлов, которые задают конкретный прогон. Учёный правит .nml в редакторе, кладёт его рядом с результатами, версионирует в git — и прогон полностью воспроизводим по этому файлу.
! Несколько namelist-групп в одном файле run.nml:
&grid
nx = 256
ny = 256
dx = 0.01
/
&physics
viscosity = 1.0e-5
use_gravity = .true.
/
&output
save_every = 100
outdir = "results/run42"
/
Программа читает группы по очереди, каждую своим read(u, nml=имя), и параметры аккуратно распределяются по модулям: группа grid настраивает модуль сетки, physics — модуль физики, и так далее. Несколько групп в одном файле — обычная практика, отражающая модульную структуру самого кода. Устойчивость namelist к изменениям тут особенно ценна: добавили в новую версию модели параметр — старые .nml-файлы продолжают работать, новый параметр просто берёт значение по умолчанию. Это позволяет годами накапливать архив входных файлов экспериментов, оставаясь совместимым. Дополнительный плюс — самодокументируемость: записав текущую конфигурацию через write(*, nml=...), программа выдаёт полный снимок всех своих настроек, который можно сохранить в лог прогона. Когда спустя годы нужно понять, с какими параметрами получен тот или иной результат, этот снимок бесценен. Именно сочетание читаемости, версионируемости, устойчивости и самодокументируемости сделало namelist де-факто стандартом, который не вытеснили ни XML, ни JSON, ни YAML — для научного Fortran-кода он остаётся самым естественным и проверенным способом конфигурации.
Выбор механизма ввода под задачу
Подытоживая, выстроим ясную картину того, какой механизм ввода-вывода выбирать под какую задачу, — это практический навык, экономящий массу сил. Для отладочной печати и быстрых черновых распечаток — списочный вывод print *: минимум усилий, не нужно подбирать формат, результат читаем человеком. Для чтения свободных рукописных данных (несколько чисел, простые входные файлы) — списочный ввод read *: гибко разбирает значения по разделителям. Для конфигурации программы с множеством параметров — namelist: именованные параметры, порядок не важен, значения по умолчанию, устойчивость к добавлению, человекочитаемость и версионируемость. Для аккуратных отчётов и файлов фиксированного формата — форматный вывод с дескрипторами: точный контроль вёрстки. Для сохранения больших данных без потери точности — неформатный или stream-вывод: скорость и битовая точность. Для переносимого обмена научными данными между системами — специализированные форматы (NetCDF, HDF5): самоописываемость и независимость от платформы. Эта таблица соответствий — часть профессиональной грамотности вычислителя.
| Задача | Механизм |
| Отладка, черновая печать | списочный print * |
| Чтение свободных данных | списочный read * |
| Конфигурация программы | namelist |
| Отчёты, фиксированный формат | форматный (дескрипторы) |
| Сохранение больших данных | неформатный / stream |
| Переносимый обмен | NetCDF / HDF5 |
Главная ошибка новичков — применять один механизм для всего: например, форматировать вручную там, где идеален namelist, или использовать списочный вывод для файла, который потом читает другая программа по позициям. Понимание, что у Fortran есть богатый арсенал средств ввода-вывода, каждое со своей нишей, и умение выбрать подходящее под конкретную потребность — это то, что отличает зрелый расчётный код. Списочный ввод-вывод и namelist, рассмотренные в этом уроке, занимают в этом арсенале важные, но конкретные ниши: первый — для удобства и черновиков, второй — для конфигурации; и применять их стоит именно там, оставляя точную вёрстку форматному выводу, а объёмные данные — бинарным форматам.
Частые ошибки
- Использовать списочный вывод для межпрограммного обмена. Его ширина и форма зависят от компилятора; для файлов, читаемых по позициям, нужен форматный или неформатный I/O.
- Забыть завершающий
/в namelist-файле. Блок namelist обязан кончаться косой чертой; без неё чтение зависнет в ожидании конца группы. - Опечатка в имени переменной namelist. Имя во входе должно точно совпадать с объявленным (регистр обычно не важен); неизвестное имя часто вызывает ошибку чтения.
- Полагать, что namelist сохраняет точность как бинарный файл. Это текст; для точного сохранения состояния между прогонами используйте неформатный I/O.
- Объявлять namelist-переменные внутри процедуры без
save, ожидая, что значения по умолчанию переживут вызовы. Помните об области видимости и времени жизни переменных.
Итоги
- Списочный I/O (
print */read *) удобен и краток — идеален для отладки и свободных данных, но непредсказуем по форме. - Списочный ввод понимает пробелы/запятые-разделители, повторители
n*vи пропуски — гибок для рукописных входных файлов. - namelist связывает значения с именами: блок
&имя ... /с парамиимя=значение, порядок не важен, опущенные берут значения по умолчанию. - namelist — стандартный механизм конфигурации научных программ: человекочитаемый, версионируемый, устойчивый к добавлению параметров.
- Ни списочный, ни namelist не сохраняют двоичную точность — для этого нужен неформатный вывод.