Три способа обертывания — начало работы
Обертывание функций 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:
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 - Function signature:
fib(a,[n])
Required arguments:
a : input rank-1 array('d') with bounds (n)
Optional arguments:
n := len(a) input int
>>> 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) fib:n=10 Traceback (most recent call last): File "<stdin>", line 1, in ? fib.error: (len(a)>=n) failed for 1st keyword n >>>
Это демонстрирует одну из полезных функций F2PY, заключающуюся в том, что 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после вызова Fortran-функцииFIB, должен быть возвращен в 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__
fib - Function signature:
a = fib(n)
Required arguments:
n : input int
Return objects:
a : rank-1 array('d') with bounds (n)
>>> print fib2.fib(8)
[ 0. 1. 1. 2. 3. 5. 8. 13.]
Примечание
- Очевидно, что сигнатура
fib2.fibтеперь более точно соответствует намерениям Fortran-подпрограммыFIB: при заданном числе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__
fib - Function signature:
a = fib(n)
Required arguments:
n : input int
Return objects:
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.18/f2py/getting-started.html