Три способа обертывания - начало работы
Обертывание функций 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–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/f2py/f2py.getting-started.html