Расширенные случаи использования F2PY#

Добавление пользовательских функций в модули, сгенерированные F2PY#

Пользовательские функции Python C/API могут быть определены внутри файлов сигнатур с помощью usercode и pymethoddef операторы (они должны использоваться внутри python module блок). Например, следующий файл сигнатур spam.pyf

!    -*- f90 -*-
python module spam
    usercode '''
  static char doc_spam_system[] = "Execute a shell command.";
  static PyObject *spam_system(PyObject *self, PyObject *args)
  {
    char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return Py_BuildValue("i", sts);
  }
    '''
    pymethoddef '''
    {"system",  spam_system, METH_VARARGS, doc_spam_system},
    '''
end python module spam

оборачивает C-функцию библиотеки system():

f2py -c spam.pyf

В Python это затем можно использовать как:

>>> import spam
>>> status = spam.system('whoami')
pearu
>>> status = spam.system('blah')
sh: line 1: blah: command not found

Добавление пользовательских переменных#

Следующий пример иллюстрирует, как добавить пользовательские переменные в модуль расширения, сгенерированный F2PY, путем изменения словаря модуля, сгенерированного F2PY. Рассмотрим следующий файл сигнатуры (скомпилированный с f2py -c var.pyf):

!    -*- f90 -*-
python module var
  usercode '''
    int BAR = 5;
  '''
  interface
    usercode '''
      PyDict_SetItemString(d,"BAR",PyLong_FromLong(BAR));
    '''
  end interface
end python module

Обратите внимание, что второй usercode оператор должен быть определён внутри interface блок и словарь модуля доступен через переменную d (см. varmodule.c сгенерировано f2py var.pyf для дополнительных деталей).

Использование в Python:

>>> import var
>>> var.BAR
5

Работа с спецификаторами KIND#

В настоящее время F2PY может обрабатывать только spec>(kind=) объявления, где является числовым целым числом (например, 1, 2, 4,…), но не вызовом функции KIND(..) или любое другое выражение. F2PY нужно знать, какой соответствующий тип C будет, и общее решение для этого было бы слишком сложным для реализации.

Однако F2PY предоставляет возможность преодолеть эту трудность, а именно, пользователи могут определить свои собственные отображения <тип Fortran> в <тип C>. Например, если код Fortran 90 содержит:

REAL(kind=KIND(0.0D0)) ...

затем создайте файл отображения, содержащий словарь Python:

{'real': {'KIND(0.0D0)': 'double'}}

например.

Используйте --f2cmap опция командной строки для передачи имени файла в F2PY. По умолчанию F2PY предполагает, что имя файла - .f2py_f2cmap в текущей рабочей директории.

В более общем случае файл f2cmap должен содержать словарь с элементами:

<Fortran typespec> : {<selector_expr>:<C type>}

который определяет соответствие между типом Fortran:

<Fortran typespec>([kind=]<selector_expr>)

и соответствующий . может быть одним из следующих:

double
float
long_double
char
signed_char
unsigned_char
short
unsigned_short
int
long
long_long
unsigned
complex_float
complex_double
complex_long_double
string

Например, для файла Fortran func1.f содержащий:

      subroutine func1(n, x, res)
        use, intrinsic :: iso_fortran_env, only: int64, real64
        implicit none
        integer(int64), intent(in) :: n
        real(real64), intent(in) :: x(n)
        real(real64), intent(out) :: res
Cf2py   intent(hide) :: n
        res = sum(x)
      end

Чтобы преобразовать int64 и real64 к допустимым C типы данных, .f2py_f2cmap файл со следующим содержимым может быть создан в текущем каталоге:

dict(real=dict(real64='double'), integer=dict(int64='long long'))

и создайте модуль как обычно. F2PY проверяет, если .f2py_f2cmap файл присутствует в текущем каталоге и будет использовать его для отображения KIND спецификаторы для C типы данных.

f2py -c func1.f -m func1

В качестве альтернативы файл сопоставления может быть сохранён под любым другим именем, например mapfile.txt, и эта информация может быть передана в F2PY с использованием --f2cmap опция.

f2py -c func1.f -m func1 --f2cmap mapfile.txt

Для получения дополнительной информации см. исходный код F2Py numpy/f2py/capi_maps.py.

Строки символов#

Строки символов предполагаемой длины#

В Fortran аргументы строк символов предполагаемой длины объявляются как character*(*) или character(len=*), то есть длина таких аргументов определяется фактическими строковыми аргументами во время выполнения. Для intent(in) аргументов, этот недостаток информации о длине не создаёт проблем для f2py при построении функциональных функций-обёрток. Однако, для intent(out) аргументы, отсутствие информации о длине проблематично для обёрток, сгенерированных f2py, потому что нет информации о размере для создания буферов памяти для таких аргументов, и F2PY предполагает, что длина равна 0. В зависимости от того, как указана длина строк символов предполагаемой длины, существуют способы обойти эту проблему, как показано ниже.

Если длина character*(*) выходной аргумент определяется состоянием других входных аргументов, необходимое соединение может быть установлено в файле сигнатуры или внутри комментария f2py, добавив дополнительное объявление для соответствующего аргумента, которое указывает длину в части селектора символов. Например, рассмотрим файл Fortran asterisk1.f90:

subroutine foo1(s)
  character*(*), intent(out) :: s
  !f2py character(f2py_len=12) s
  s = "123456789A12"
end subroutine foo1

Скомпилируйте его с помощью f2py -c asterisk1.f90 -m asterisk1 а затем в Python:

>>> import asterisk1
>>> asterisk1.foo1()
b'123456789A12'

нет доступного отладочного колеса, character(f2py_len=12) s интерпретируется только f2py и в f2py_len= спецификация, позволяющая использовать C-выражения в качестве значения длины.

В следующем примере:

subroutine foo2(s, n)
  character(len=*), intent(out) :: s
  integer, intent(in) :: n
  !f2py character(f2py_len=n), depend(n) :: s
  s = "123456789A123456789B"(1:n)
end subroutine foo2

длина предполагаемой строки вывода зависит от входного аргумента n, после обертывания с помощью F2PY, в Python:

>>> import asterisk
>>> asterisk.foo2(2)
b'12'
>>> asterisk.foo2(12)
b'123456789A12'
>>>