Три способа обертки - начало работы#
Обёртывание функций Fortran или C в Python с использованием F2PY состоит из следующих шагов:
Создание так называемого файл подписи который содержит описания обёрток для функций на Fortran или C, также называемые сигнатурами функций. Для подпрограмм на Fortran, F2PY может создать начальный файл сигнатур, сканируя исходные коды Fortran и отслеживая всю необходимую информацию для создания функций-обёрток.
При необходимости, файлы сигнатур, созданные F2PY, можно редактировать для оптимизации функций-обёрток, что может сделать их «умнее» и более «питоничными».
F2PY читает файл сигнатуры и записывает модуль Python C/API, содержащий привязки Fortran/C/Python.
F2PY компилирует все исходники и создает модуль расширения, содержащий обертки.
При сборке модулей расширения F2PY использует
mesonи ранее использовалnumpy.distutilsДля различных систем сборки см. F2PY и системы сборки.
Примечание
См. 1 Миграция на meson для информации о миграции.
В зависимости от вашей операционной системы, вам может потребоваться установить заголовки разработки Python (которые предоставляют файл
Python.h) отдельно. В дистрибутивах Linux на основе Debian этот пакет должен называтьсяpython3-dev, в дистрибутивах на основе Fedora этоpython3-devel. Для macOS, в зависимости от способа установки Python, ваши результаты могут отличаться. В Windows заголовки обычно уже установлены, см. F2PY и Windows.
Примечание
F2PY поддерживает все операционные системы, на которых тестируется SciPy, поэтому их панель системных зависимостей является хорошим справочником.
В зависимости от ситуации эти шаги могут быть выполнены одной составной командой или пошагово; в последнем случае некоторые шаги могут быть опущены или объединены с другими.
Ниже мы описываем три типичных подхода использования F2PY с Fortran 77. Их можно читать в порядке увеличения усилий, но они также учитывают разные уровни доступа в зависимости от того, можно ли свободно изменять код Fortran.
Следующий пример кода 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
Примечание
F2PY анализирует сигнатуры Fortran/C для создания функций-оберток для использования с Python. Однако это не компилятор и не проверяет дополнительные ошибки в исходном коде, а также не реализует все стандарты языка. Некоторые ошибки могут пройти незамеченно (или как предупреждения) и должны быть проверены пользователем.
Быстрый способ#
Самый быстрый способ обернуть подпрограмму Fortran FIB для использования в Python — это запустить
python -m numpy.f2py -c fib1.f -m fib1
или, альтернативно, если f2py доступен инструмент командной строки,
f2py -c fib1.f -m fib1
Примечание
Поскольку f2py команда может быть недоступна во всех системах, особенно в
Windows, мы будем использовать python -m numpy.f2py команду на протяжении этого руководства.
Эта команда компилирует и оборачивает fib1.f (-c) для создания модуля расширения fib1.so (-m) в текущем каталоге. Список параметров командной строки
можно увидеть, выполнив python -m numpy.f2pyТеперь в Python подпрограмма Fortran FIB доступен через fib1.fib:
>>> import numpy as np
>>> 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 = np.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 = np.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 "
" , line 1, infib.error: (len(a)>=n) failed for 1st keyword n: fib:n=10 >>> F2PY реализует базовые проверки совместимости между связанными аргументами, чтобы избежать неожиданных сбоев.
Когда массив NumPy, который Fortran непрерывный и имеет
dtypeсоответствующий предполагаемому Fortran-типу, используется как входной аргумент массива, тогда его C-указатель напрямую передаётся в Fortran.В противном случае F2PY создает непрерывную копию (с правильным
dtype) входного массива и передает указатель C на копию подпрограмме Fortran. В результате любые возможные изменения (копии) входного массива не влияют на исходный аргумент, как показано ниже:>>> a = np.ones(8, 'i') >>> fib1.fib(a) >>> print(a) [1 1 1 1 1 1 1 1]
Очевидно, это неожиданно, так как Fortran обычно передаёт по ссылке. То, что приведённый выше пример сработал с
dtype=floatсчитается случайным.F2PY предоставляет
intent(inplace)атрибут, который изменяет атрибуты входного массива так, чтобы любые изменения, внесённые подпрограммой Fortran, отражались во входном аргументе. Например, если указатьintent(inplace) aдиректива (см. Атрибуты для подробностей), тогда приведённый выше пример будет читаться как:>>> a = np.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 могут привести к трудно находимым ошибкам, не говоря уже о том, что код будет менее читаемым, когда все необходимые проверки типов будут реализованы.
Хотя подход к обёртыванию фортран-подпрограмм для Python, обсуждаемый до сих пор, очень прост, он имеет несколько недостатков (см. комментарии выше). Недостатки связаны с тем, что нет способа для F2PY определить фактическое назначение аргументов; то есть существует неоднозначность в различении входных и выходных аргументов. Следовательно, F2PY по умолчанию предполагает, что все аргументы являются входными.
Существуют способы (см. ниже) устранить эту неоднозначность, «обучая» F2PY истинным намерениям аргументов функций, и тогда F2PY способен генерировать более явные, простые в использовании и менее подверженные ошибкам обёртки для функций Fortran.
Умный способ#
Если мы хотим иметь больше контроля над тем, как F2PY будет обрабатывать интерфейс к нашему коду на Fortran, мы можем применить шаги обёртки по одному.
Сначала мы создаем файл сигнатуры из
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
Наконец, мы собираем модуль расширения с
numpy.distutilsзапустив: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 или !f2py, например), которые игнорируются компиляторами Fortran, но интерпретируются F2PY как обычные строки.
Рассмотрим изменённую версию предыдущего кода на Fortran с директивами F2PY,
сохранённую как 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.]