Три способа обертки - начало работы
Обертывание функций Fortran или C в Python с помощью F2PY состоит из следующих шагов:
- Создание так называемого файла сигнатур, содержащего описания обёрток для функций Fortran или C, также называемых сигнатурами функций. В случае Fortran-процедур F2PY может создать начальный файл сигнатур, просканировав исходные коды Fortran и получив всю необходимую информацию для создания функций-обёртки.
- Необязательно, файлы сигнатур, созданные F2PY, можно редактировать для оптимизации функций-обёртки, сделать их «умнее» и более «питоническими».
- F2PY считывает файл сигнатур и записывает модуль Python C/API, содержащий связи Fortran/C/Python.
- F2PY компилирует все исходные коды и создаёт модуль расширения, содержащий обёртки. При построении модулей расширения F2PY использует
numpy_distutils, который поддерживает множество компиляторов Fortran 77/90/95, включая Gnu, Intel, Sun Fortre, SGI MIPSpro, Absoft, NAG, Compaq и др.
В зависимости от конкретной ситуации, эти шаги можно выполнить либо одной командой, либо пошагово, некоторые шаги можно пропустить или объединить с другими.
Ниже я опишу три типичных подхода к использованию F2PY. Следующий пример кода Fortran 77 будет использован для иллюстрации; сохраните его как fib1.f:
C FILE: FIB1.F
SUBROUTINE FIB(A,N)
C
C CALCULATE FIRST N FIBONACCI NUMBERS
C
INTEGER N
REAL*8 A(N)
DO I=1,N
IF (I.EQ.1) THEN
A(I) = 0.0D0
ELSEIF (I.EQ.2) THEN
A(I) = 1.0D0
ELSE
A(I) = A(I-1) + A(I-2)
ENDIF
ENDDO
END
C END FILE FIB1.F
Быстрый способ
Самый быстрый способ обернуть подпрограмму Fortran FIB в Python — это запустить
python -m numpy.f2py -c fib1.f -m fib1
Эта команда создаёт (см. флаг -c, выполните python -m numpy.f2py без аргументов, чтобы увидеть объяснение опций командной строки) модуль расширения fib1.so (см. флаг -m) в текущий каталог. Теперь в Python подпрограмма Fortran FIB доступна через fib1.fib.
>>> import numpy
>>> import fib1
>>> print(fib1.fib.__doc__)
fib(a,[n])
Wrapper for ``fib``.
Parameters
----------
a : input rank-1 array('d') with bounds (n)
Other Parameters
----------------
n : input int, optional
Default: len(a)
>>> a = numpy.zeros(8, 'd')
>>> fib1.fib(a)
>>> print(a)
[ 0. 1. 1. 2. 3. 5. 8. 13.]
Примечание
- Обратите внимание, что F2PY обнаружил, что второй аргумент
nявляется размерностью первого массива-аргументаa. Поскольку по умолчанию все аргументы являются только входными, F2PY делает вывод, чтоnможет быть необязательным со значением по умолчаниюlen(a). -
Можно использовать различные значения для необязательного
n.>>> a1 = numpy.zeros(8, 'd') >>> fib1.fib(a1, 6) >>> print(a1) [ 0. 1. 1. 2. 3. 5. 0. 0.]
но возникает исключение, когда оно несовместимо с входным массивом
a.>>> fib1.fib(a, 10) Traceback (most recent call last): File "<stdin>", line 1, in <module> fib.error: (len(a)>=n) failed for 1st keyword n: fib:n=10 >>>
F2PY реализует базовые проверки совместимости между связанными аргументами, чтобы избежать неожиданных сбоев.
-
Когда массив NumPy, являющийся Fortran-непрерывным и имеющий тип данных, соответствующий предполагаемому типу Fortran, используется в качестве входного массива-аргумента, указатель C его напрямую передаётся в Fortran.
В противном случае F2PY создаёт непрерывную копию (с соответствующим типом данных) входного массива и передаёт указатель C копии в подпрограмму Fortran. В результате любые возможные изменения в (копии) входного массива не оказывают никакого влияния на исходный аргумент, как показано ниже:
>>> a = numpy.ones(8, 'i') >>> fib1.fib(a) >>> print(a) [1 1 1 1 1 1 1 1]
Очевидно, это не ожидаемое поведение. Тот факт, что вышеприведённый пример работал с
dtype=float, считается случайным.F2PY предоставляет атрибут
intent(inplace), который изменит атрибуты входного массива таким образом, чтобы любые изменения, внесённые процедурой Fortran, будут действовать и на входной аргумент. Например, если указатьintent(inplace) a(см. ниже, как), то вышеприведённый пример будет выглядеть так:>>> a = numpy.ones(8, 'i') >>> fib1.fib(a) >>> print(a) [ 0. 1. 1. 2. 3. 5. 8. 13.]
Однако рекомендуемый способ возврата изменений, внесённых подпрограммой Fortran, в Python — это использование атрибута
intent(out). Это более эффективный и чистый способ. - Использование
fib1.fibв Python очень похоже на использованиеFIBв Fortran. Однако использование in situ выходных аргументов в Python — плохой стиль, так как в Python нет механизма защиты от неправильных типов аргументов. При использовании Fortran или C компиляторы естественным образом обнаруживают любые несоответствия типов во время компиляции, но в Python типы должны проверяться во время выполнения. Таким образом, использование in situ выходных аргументов в Python может привести к трудноуловимым ошибкам, не говоря уже о том, что код будет менее читабельным при реализации всех необходимых проверок типов.
Хотя показанный способ обертывания процедур Fortran в Python очень прост, у него есть несколько недостатков (см. комментарии выше). Эти недостатки связаны с тем, что F2PY не может определить, какое именно намерение лежит в основе того или иного аргумента, является ли он входным, выходным, или обоими, или чем-то другим. Поэтому F2PY по умолчанию предполагает, что все аргументы являются входными.
Однако существуют способы (см. ниже), как «научить» F2PY истинному назначению аргументов функции; после этого F2PY может генерировать более питонические (более явные, более удобные в использовании и менее подверженные ошибкам) обёртки для функций Fortran.
Умный способ
Давайте применим шаги обертывания функций Fortran в Python по одному.
-
Сначала мы создадим файл сигнатур из
fib1.f, выполнивpython -m numpy.f2py fib1.f -m fib2 -h fib1.pyf
Файл сигнатур сохраняется в
fib1.pyf(см. флаг-h) и его содержимое показано ниже.! -*- f90 -*- python module fib2 ! in interface ! in :fib2 subroutine fib(a,n) ! in :fib2:fib1.f real*8 dimension(n) :: a integer optional,check(len(a)>=n),depend(a) :: n=len(a) end subroutine fib end interface end python module fib2 ! This file was auto-generated with f2py (version:2.28.198-1366). ! See http://cens.ioc.ee/projects/f2py2e/ -
Далее, мы научим F2PY, что аргумент
nявляется входным аргументом (использовать атрибутintent(in)) и что результат, то есть содержимоеaпосле вызова функции FortranFIB, должен быть возвращён в Python (использовать атрибутintent(out)). Кроме того, массивaдолжен быть создан динамически с размером, заданным входным аргументомn(использовать атрибутdepend(n)для указания зависимостей).Содержимое изменённой версии
fib1.pyf(сохранённой какfib2.pyf) выглядит следующим образом:! -*- f90 -*- python module fib2 interface subroutine fib(a,n) real*8 dimension(n),intent(out),depend(n) :: a integer intent(in) :: n end subroutine fib end interface end python module fib2 -
И, наконец, мы создаём модуль расширения, выполнив
python -m numpy.f2py -c fib2.pyf fib1.f
В Python:
>>> import fib2
>>> print(fib2.fib.__doc__)
a = fib(n)
Wrapper for ``fib``.
Parameters
----------
n : input int
Returns
-------
a : rank-1 array('d') with bounds (n)
>>> print(fib2.fib(8))
[ 0. 1. 1. 2. 3. 5. 8. 13.]
Примечание
- Очевидно, что сигнатура
fib2.fibтеперь более точно соответствует намерениям подпрограммы FortranFIB: задавая числоn,fib2.fibвозвращает первыеnчисел Фибоначчи в виде массива NumPy. Также новая сигнатура Pythonfib2.fibисключает любые неожиданности, с которыми мы столкнулись сfib1.fib. - Обратите внимание, что по умолчанию использование единственного
intent(out)также подразумеваетintent(hide). Аргументы, для которых указан атрибутintent(hide), не будут перечислены в списке аргументов функции-обёртки.
Быстрый и умный способ
«Умный» способ обертывания функций Fortran, как описано выше, подходит для обертывания (например, стороннего) кода Fortran, для которого изменения в исходных кодах нежелательны или даже невозможны.
Однако, если редактирование кода Fortran допустимо, то в большинстве случаев можно пропустить генерацию промежуточного файла сигнатур. А именно, можно вставить специфические атрибуты F2PY непосредственно в исходный код Fortran, используя так называемую директиву F2PY. Директива F2PY определяет специальные комментарии (начинающиеся с Cf2py, например), которые игнорируются компиляторами Fortran, но F2PY интерпретирует их как обычные строки.
Вот изменённая версия предыдущего кода Fortran; сохраните его как fib3.f.
C FILE: FIB3.F
SUBROUTINE FIB(A,N)
C
C CALCULATE FIRST N FIBONACCI NUMBERS
C
INTEGER N
REAL*8 A(N)
Cf2py intent(in) n
Cf2py intent(out) a
Cf2py depend(n) a
DO I=1,N
IF (I.EQ.1) THEN
A(I) = 0.0D0
ELSEIF (I.EQ.2) THEN
A(I) = 1.0D0
ELSE
A(I) = A(I-1) + A(I-2)
ENDIF
ENDDO
END
C END FILE FIB3.F
Модуль расширения можно теперь создать одной командой:
python -m numpy.f2py -c -m fib3 fib3.f
Обратите внимание, что полученная обертка для FIB такая же «умная», как и в предыдущем случае:
>>> import fib3
>>> print(fib3.fib.__doc__)
a = fib(n)
Wrapper for ``fib``.
Parameters
----------
n : input int
Returns
-------
a : rank-1 array('d') with bounds (n)
>>> print(fib3.fib(8))
[ 0. 1. 1. 2. 3. 5. 8. 13.]
© 2005–2020 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.19/f2py/f2py.getting-started.html