Три способа обертки — начало работы
Обертка функций Fortran или C в Python с помощью F2PY состоит из следующих шагов:
- Создание так называемого файла сигнатур, содержащего описания обёрток для функций Fortran или C, также называемых сигнатурами функций. В случае процедур Fortran, F2PY может создать начальный файл сигнатур, просканировав исходный код Fortran и вычленив всю необходимую информацию для создания обёртки функций.
- По желанию, созданные F2PY файлы сигнатур могут быть отредактированы для оптимизации обёртки функций, чтобы сделать их «умнее» и более «pythonic».
- 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 может сгенерировать более Pythonic (более явные, удобные в использовании и менее подверженные ошибкам) обёртки для функций 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–2021 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.20/f2py/f2py.getting-started.html