F2PY 範例#

以下是一些 F2PY 用法範例。此列表並不詳盡,但可用作包裝您自己程式碼的起點。

注意

尋找範例的最佳地點是 NumPy issue tracker,或是 f2py 的測試案例。更多用例在 樣板減少與範本化 中。

F2PY 逐步解說:基本擴充模組#

為基本擴充模組建立原始碼#

考慮以下子程序,包含在名為 add.f 的檔案中

C
      SUBROUTINE ZADD(A,B,C,N)
C
      DOUBLE COMPLEX A(*)
      DOUBLE COMPLEX B(*)
      DOUBLE COMPLEX C(*)
      INTEGER N
      DO 20 J = 1, N
         C(J) = A(J)+B(J)
 20   CONTINUE
      END

此常式僅將兩個連續陣列中的元素相加,並將結果放入第三個陣列中。所有三個陣列的記憶體必須由呼叫常式提供。可以使用 f2py 自動產生此常式的非常基本介面

python -m numpy.f2py -m add add.f

此命令將在目前目錄中產生一個名為 addmodule.c 的擴充模組。現在可以編譯此擴充模組,並像任何其他擴充模組一樣從 Python 中使用。

建立已編譯的擴充模組#

您也可以讓 f2py 同時編譯 add.f 以及產生的擴充模組,只留下一個可以從 Python 匯入的共享函式庫擴充檔案

python -m numpy.f2py -c -m add add.f

此命令會產生與您的平台相容的 Python 擴充模組。然後可以從 Python 匯入此模組。它將包含 add 中每個子程序的函式。每個函式的 docstring 都包含有關如何呼叫模組函式的資訊

>>> import add
>>> print(add.zadd.__doc__)
zadd(a,b,c,n)

Wrapper for ``zadd``.

Parameters
----------
a : input rank-1 array('D') with bounds (*)
b : input rank-1 array('D') with bounds (*)
c : input rank-1 array('D') with bounds (*)
n : input int

改進基本介面#

預設介面非常字面地將 Fortran 程式碼翻譯成 Python。Fortran 陣列引數會轉換為 NumPy 陣列,而整數引數應對應到 C 整數。介面將嘗試將所有引數轉換為其所需的類型(和形狀),如果失敗則發出錯誤。但是,由於 f2py 對引數的語意一無所知(例如 C 是輸出,而 n 實際上應該與陣列大小相符),因此可能會以可能導致 Python 崩潰的方式濫用此函式。例如

>>> add.zadd([1, 2, 3], [1, 2], [3, 4], 1000)

將導致大多數系統上的程式崩潰。在底層,列表正在轉換為陣列,但隨後會告知底層 add 函式循環超出已分配記憶體的邊界。

為了改進介面,f2py 支援指示詞。這是透過建構簽名檔來完成的。通常最好從 f2py 在該檔案中產生的介面開始,這些介面對應於預設行為。若要讓 f2py 產生介面檔案,請使用 -h 選項

python -m numpy.f2py -h add.pyf -m add add.f

此命令會在目前目錄中建立 add.pyf 檔案。此檔案中對應於 zadd 的區段是

subroutine zadd(a,b,c,n) ! in :add:add.f
   double complex dimension(*) :: a
   double complex dimension(*) :: b
   double complex dimension(*) :: c
   integer :: n
end subroutine zadd

透過放置 intent 指示詞和檢查程式碼,可以大幅清理介面,使 Python 模組函式更易於使用,且對格式錯誤的輸入更具備穩健性。

subroutine zadd(a,b,c,n) ! in :add:add.f
   double complex dimension(n) :: a
   double complex dimension(n) :: b
   double complex intent(out),dimension(n) :: c
   integer intent(hide),depend(a) :: n=len(a)
end subroutine zadd

intent 指示詞 intent(out) 用於告知 f2py,c 是一個輸出變數,應由介面建立,然後再傳遞給底層程式碼。intent(hide) 指示詞告知 f2py 不允許使用者指定變數 n,而是從 a 的大小取得。depend( a ) 指示詞是必要的,以告知 f2py n 的值取決於輸入 a(因此它不會嘗試建立變數 n,直到建立變數 a 為止)。

修改 add.pyf 後,可以透過編譯 add.fadd.pyf 來產生新的 Python 模組檔案

python -m numpy.f2py -c add.pyf add.f

新介面的 docstring 是

>>> import add
>>> print(add.zadd.__doc__)
c = zadd(a,b)

Wrapper for ``zadd``.

Parameters
----------
a : input rank-1 array('D') with bounds (n)
b : input rank-1 array('D') with bounds (n)

Returns
-------
c : rank-1 array('D') with bounds (n)

現在,可以以更穩健的方式呼叫函式

>>> add.zadd([1, 2, 3], [4, 5, 6])
array([5.+0.j, 7.+0.j, 9.+0.j])

請注意發生的自動轉換為正確格式。

在 Fortran 原始碼中插入指示詞#

也可以透過將變數指示詞作為特殊註解放置在原始 Fortran 程式碼中,來自動產生上一節的穩健介面。

注意

對於正在積極開發 Fortran 程式碼的專案,這可能是首選方法。

因此,如果修改原始碼以包含

C
      SUBROUTINE ZADD(A,B,C,N)
C
CF2PY INTENT(OUT) :: C
CF2PY INTENT(HIDE) :: N
CF2PY DOUBLE COMPLEX :: A(N)
CF2PY DOUBLE COMPLEX :: B(N)
CF2PY DOUBLE COMPLEX :: C(N)
      DOUBLE COMPLEX A(*)
      DOUBLE COMPLEX B(*)
      DOUBLE COMPLEX C(*)
      INTEGER N
      DO 20 J = 1, N
         C(J) = A(J) + B(J)
 20   CONTINUE
      END

然後,可以使用以下命令編譯擴充模組

python -m numpy.f2py -c -m add add.f

函式 add.zadd 的結果簽名與先前建立的簽名完全相同。如果原始原始碼包含 A(N) 而不是 A(*),依此類推 BC,那麼幾乎相同的介面可以透過在原始碼中放置 INTENT(OUT) :: C 註解行來獲得。唯一的區別是 N 將是一個可選輸入,預設為 A 的長度。

篩選範例#

此範例顯示一個函式,該函式使用固定的平均濾波器篩選雙精度浮點數的二維陣列。從此範例中應清楚了解使用 Fortran 索引到多維陣列的優勢。

C
      SUBROUTINE DFILTER2D(A,B,M,N)
C
      DOUBLE PRECISION A(M,N)
      DOUBLE PRECISION B(M,N)
      INTEGER N, M
CF2PY INTENT(OUT) :: B
CF2PY INTENT(HIDE) :: N
CF2PY INTENT(HIDE) :: M
      DO 20 I = 2,M-1
         DO 40 J = 2,N-1
            B(I,J) = A(I,J) +
     &           (A(I-1,J)+A(I+1,J) +
     &           A(I,J-1)+A(I,J+1) )*0.5D0 +
     &           (A(I-1,J-1) + A(I-1,J+1) +
     &           A(I+1,J-1) + A(I+1,J+1))*0.25D0
 40      CONTINUE
 20   CONTINUE
      END

可以使用以下命令將此程式碼編譯並連結到名為 filter 的擴充模組中

python -m numpy.f2py -c -m filter filter.f

這將在目前目錄中產生一個擴充模組,其中包含一個名為 dfilter2d 的函式,該函式傳回輸入的篩選版本。

depends 關鍵字範例#

考慮以下程式碼,儲存在檔案 myroutine.f90

subroutine s(n, m, c, x)
	implicit none
  	integer, intent(in) :: n, m
  	real(kind=8), intent(out), dimension(n,m) :: x
  	real(kind=8), intent(in) :: c(:)

	x = 0.0d0
	x(1, 1) = c(1)

end subroutine s

使用 python -m numpy.f2py -c myroutine.f90 -m myroutine 包裝它,我們可以在 Python 中執行以下操作

>>> import numpy as np
>>> import myroutine
>>> x = myroutine.s(2, 3, np.array([5, 6, 7]))
>>> x
array([[5., 0., 0.],
   [0., 0., 0.]])

現在,我們將先為此子程序建立簽名檔,而不是直接產生擴充模組。這是多步驟擴充模組產生的常見模式。在此情況下,執行

python -m numpy.f2py myroutine.f90 -m myroutine -h myroutine.pyf

會產生以下簽名檔

!    -*- f90 -*-
! Note: the context of this file is case sensitive.

python module myroutine ! in 
    interface  ! in :myroutine
        subroutine s(n,m,c,x) ! in :myroutine:myroutine.f90
            integer intent(in) :: n
            integer intent(in) :: m
            real(kind=8) dimension(:),intent(in) :: c
            real(kind=8) dimension(n,m),intent(out),depend(m,n) :: x
        end subroutine s
    end interface 
end python module myroutine

! This file was auto-generated with f2py (version:1.23.0.dev0+120.g4da01f42d).
! See:
! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e

現在,如果我們執行 python -m numpy.f2py -c myroutine.pyf myroutine.f90,我們會看到錯誤;請注意,簽名檔包含 xdepend(m,n) 陳述式,這是沒有必要的。實際上,編輯上面的檔案以讀取

!    -*- f90 -*-
! Note: the context of this file is case sensitive.

python module myroutine ! in 
    interface  ! in :myroutine
        subroutine s(n,m,c,x) ! in :myroutine:myroutine.f90
            integer intent(in) :: n
            integer intent(in) :: m
            real(kind=8) dimension(:),intent(in) :: c
            real(kind=8) dimension(n,m),intent(out) :: x
        end subroutine s
    end interface 
end python module myroutine

! This file was auto-generated with f2py (version:1.23.0.dev0+120.g4da01f42d).
! See:
! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e

並執行 f2py -c myroutine.pyf myroutine.f90 會產生正確的結果。

延伸閱讀#