進階 F2PY 使用案例#

將使用者定義的函式新增至 F2PY 產生的模組#

使用者定義的 Python C/API 函式可以使用 usercodepymethoddef 陳述式在簽名檔內定義(它們必須在 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 取得(有關更多詳細資訊,請參閱 f2py var.pyf 產生的 varmodule.c)。

在 Python 中使用

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

處理 KIND 指定符#

目前,F2PY 只能處理 <type spec>(kind=<kindselector>) 宣告,其中 <kindselector> 是數值整數(例如 1、2、4、…),但不能是函式呼叫 KIND(..) 或任何其他運算式。F2PY 需要知道對應的 C 類型是什麼,而通用的解決方案實作起來會太複雜。

然而,F2PY 提供了一個 hook 來克服這個困難,也就是說,使用者可以定義自己的 <Fortran type> 到 <C type> 對應。例如,如果 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>)

以及對應的 <C type>。<C type> 可以是以下其中之一

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

例如,對於包含 func1.f 的 Fortran 檔案

      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

為了將 int64real64 轉換為有效的 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,並且可以使用 --f2cmap 選項將此資訊傳遞給 F2PY。

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'
>>>