在 Python 中使用 F2PY 綁定#

在本頁中,您可以找到 F2PY 與 Python 和不同引數類型的完整描述和一些常見用法範例。如需更多範例和使用案例,請參閱F2PY 範例

Fortran 類型物件#

由 F2PY 產生的 Fortran/C 常式、common block 或 Fortran 90 模組資料的所有包裝器,都以 fortran 類型物件的形式暴露給 Python。常式包裝器是可呼叫的 fortran 類型物件,而 Fortran 資料的包裝器則具有指向資料物件的屬性。

所有 fortran 類型物件都有一個屬性 _cpointer,其中包含一個 PyCapsule,指向 C 層級對應的 Fortran/C 函數或變數的 C 指標。此類 PyCapsule 物件可用作 F2PY 產生的函數的回呼引數,以繞過 Python C/API 層,從 Fortran 或 C 呼叫 Python 函數。當此類函數的計算方面在 C 或 Fortran 中實作並使用 F2PY(或任何其他能夠提供包含函數的 PyCapsule 的工具)包裝時,這可能很有用。

考慮一個 Fortran 77 檔案 `ftype.f

C FILE: FTYPE.F
      SUBROUTINE FOO(N)
      INTEGER N
Cf2py integer optional,intent(in) :: n = 13
      REAL A,X
      COMMON /DATA/ A,X(3)
C     PRINT*, "IN FOO: N=",N," A=",A," X=[",X(1),X(2),X(3),"]"
      END
C END OF FTYPE.F

以及使用 f2py -c ftype.f -m ftype 建置的包裝器。

在 Python 中,您可以觀察 foodata 的類型,以及如何存取包裝的 Fortran 程式碼的個別物件。

>>> import ftype
>>> print(ftype.__doc__)
This module 'ftype' is auto-generated with f2py (version:2).
Functions:
  foo(n=13)
COMMON blocks:
  /data/ a,x(3)
.
>>> type(ftype.foo), type(ftype.data)
(<class 'fortran'>, <class 'fortran'>)
>>> ftype.foo()
 IN FOO: N= 13 A=  0. X=[  0.  0.  0.]
>>> ftype.data.a = 3
>>> ftype.data.x = [1,2,3]
>>> ftype.foo()
 IN FOO: N= 13 A=  3. X=[  1.  2.  3.]
>>> ftype.data.x[1] = 45  
>>> ftype.foo(24)
 IN FOO: N= 24 A=  3. X=[  1.  45.  3.]
>>> ftype.data.x
array([  1.,  45.,   3.], dtype=float32)

純量引數#

一般而言,F2PY 產生的包裝函數的純量引數可以是普通的 Python 純量(整數、浮點數、複數),以及純量的任意序列物件(列表、元組、陣列、字串)。在後一種情況下,序列物件的第一個元素會作為純量引數傳遞給 Fortran 常式。

注意

  • 當需要類型轉換,且可能因為縮小轉換而遺失資訊時,例如將浮點數轉換為整數或將複數轉換為浮點數時,F2PY *不會*引發例外狀況。

    • 對於複數到實數的類型轉換,僅使用複數的實部。

  • intent(inout) 純量引數假設為陣列物件,以便 *就地* 變更生效。建議使用具有正確類型的陣列,但其他類型也可以運作。進一步了解 intent 屬性

考慮以下 Fortran 77 程式碼

C FILE: SCALAR.F
      SUBROUTINE FOO(A,B)
      REAL*8 A, B
Cf2py intent(in) a
Cf2py intent(inout) b
      PRINT*, "    A=",A," B=",B
      PRINT*, "INCREMENT A AND B"
      A = A + 1D0
      B = B + 1D0
      PRINT*, "NEW A=",A," B=",B
      END
C END OF FILE SCALAR.F

並使用 f2py -c -m scalar scalar.f 包裝它。

在 Python 中

>>> import scalar
>>> print(scalar.foo.__doc__)
foo(a,b)

Wrapper for ``foo``.

Parameters
----------
a : input float
b : in/output rank-0 array(float,'d')
 
>>> scalar.foo(2, 3)   
     A=  2. B=  3.
 INCREMENT A AND B
 NEW A=  3. B=  4.
>>> import numpy
>>> a = numpy.array(2)   # these are integer rank-0 arrays
>>> b = numpy.array(3)
>>> scalar.foo(a, b)
     A=  2. B=  3.
 INCREMENT A AND B
 NEW A=  3. B=  4.
>>> print(a, b)          # note that only b is changed in situ
2 4

字串引數#

F2PY 產生的包裝函數接受幾乎任何 Python 物件作為字串引數,因為 str 適用於非字串物件。例外情況是 NumPy 陣列,當用作字串引數時,其類型代碼必須為 'S1''b'(分別對應於過時的 'c''1' 類型代碼)。有關這些類型代碼的更多資訊,請參閱純量

當字串用作 F2PY 產生的包裝函數的字串引數時,字串可以具有任意長度。如果長度大於預期,則字串會被靜默截斷。如果長度小於預期,則會配置額外記憶體並以 \0 填滿。

由於 Python 字串是不可變的,因此 intent(inout) 引數需要字串的陣列版本,以便 *就地* 變更生效。

考慮以下 Fortran 77 程式碼

C FILE: STRING.F
      SUBROUTINE FOO(A,B,C,D)
      CHARACTER*5 A, B
      CHARACTER*(*) C,D
Cf2py intent(in) a,c
Cf2py intent(inout) b,d
      PRINT*, "A=",A
      PRINT*, "B=",B
      PRINT*, "C=",C
      PRINT*, "D=",D
      PRINT*, "CHANGE A,B,C,D"
      A(1:1) = 'A'
      B(1:1) = 'B'
      C(1:1) = 'C'
      D(1:1) = 'D'
      PRINT*, "A=",A
      PRINT*, "B=",B
      PRINT*, "C=",C
      PRINT*, "D=",D
      END
C END OF FILE STRING.F

並使用 f2py -c -m mystring string.f 包裝它。

Python 會話

>>> import mystring
>>> print(mystring.foo.__doc__)
foo(a,b,c,d)

Wrapper for ``foo``.

Parameters
----------
a : input string(len=5)
b : in/output rank-0 array(string(len=5),'c')
c : input string(len=-1)
d : in/output rank-0 array(string(len=-1),'c')

>>> from numpy import array
>>> a = array(b'123\0\0')
>>> b = array(b'123\0\0')
>>> c = array(b'123')
>>> d = array(b'123')
>>> mystring.foo(a, b, c, d)
 A=123
 B=123
 C=123
 D=123
 CHANGE A,B,C,D
 A=A23
 B=B23
 C=C23
 D=D23
>>> a[()], b[()], c[()], d[()]
(b'123', b'B23', b'123', b'D2')

陣列引數#

一般而言,F2PY 產生的包裝函數的陣列引數接受可以轉換為 NumPy 陣列物件的任意序列。有兩個值得注意的例外情況

  • intent(inout) 陣列引數必須始終是 適當連續的 且具有相容的 dtype,否則會引發例外狀況。

  • intent(inplace) 陣列引數如果引數的類型與預期類型不同,則會 *就地* 變更(請參閱 intent(inplace) 屬性 以取得更多資訊)。

一般而言,如果 NumPy 陣列是 適當連續的 且具有正確的類型,則會直接傳遞給包裝的 Fortran/C 函數。否則,會建立輸入陣列的元素級副本,並將該副本(適當連續且具有正確類型)用作陣列引數。

通常,無需擔心陣列在記憶體中的儲存方式,以及包裝的函數(無論是 Fortran 還是 C 函數)是否假定一種或另一種儲存順序。F2PY 會自動確保包裝的函數獲得具有正確儲存順序的引數;底層演算法旨在僅在絕對必要時才複製陣列。但是,當處理尺寸接近電腦實體記憶體大小的非常大的多維輸入陣列時,必須注意確保使用適當連續且類型正確的引數。

若要在將輸入陣列傳遞給 Fortran 常式之前將其轉換為 column major 儲存順序,請使用函數 numpy.asfortranarray

考慮以下 Fortran 77 程式碼

C FILE: ARRAY.F
      SUBROUTINE FOO(A,N,M)
C
C     INCREMENT THE FIRST ROW AND DECREMENT THE FIRST COLUMN OF A
C
      INTEGER N,M,I,J
      REAL*8 A(N,M)
Cf2py intent(in,out,copy) a
Cf2py integer intent(hide),depend(a) :: n=shape(a,0), m=shape(a,1)
      DO J=1,M
         A(1,J) = A(1,J) + 1D0
      ENDDO
      DO I=1,N
         A(I,1) = A(I,1) - 1D0
      ENDDO
      END
C END OF FILE ARRAY.F

並使用 f2py -c -m arr array.f -DF2PY_REPORT_ON_ARRAY_COPY=1 包裝它。

在 Python 中

>>> import arr
>>> from numpy import asfortranarray
>>> print(arr.foo.__doc__)
a = foo(a,[overwrite_a])

Wrapper for ``foo``.

Parameters
----------
a : input rank-2 array('d') with bounds (n,m)

Other Parameters
----------------
overwrite_a : input int, optional
    Default: 0

Returns
-------
a : rank-2 array('d') with bounds (n,m)

>>> a = arr.foo([[1, 2, 3],
...              [4, 5, 6]])
created an array from object
>>> print(a)
[[ 1.  3.  4.]
 [ 3.  5.  6.]]
>>> a.flags.c_contiguous
False
>>> a.flags.f_contiguous
True
# even if a is proper-contiguous and has proper type,
# a copy is made forced by intent(copy) attribute
# to preserve its original contents
>>> b = arr.foo(a)
copied an array: size=6, elsize=8
>>> print(a)
[[ 1.  3.  4.]
 [ 3.  5.  6.]]
>>> print(b)
[[ 1.  4.  5.]
 [ 2.  5.  6.]]
>>> b = arr.foo(a, overwrite_a = 1) # a is passed directly to Fortran
...                                 # routine and its contents is discarded
... 
>>> print(a)
[[ 1.  4.  5.]
 [ 2.  5.  6.]]
>>> print(b)
[[ 1.  4.  5.]
 [ 2.  5.  6.]]
>>> a is b                          # a and b are actually the same objects
True
>>> print(arr.foo([1, 2, 3]))       # different rank arrays are allowed
created an array from object
[ 1.  1.  2.]
>>> print(arr.foo([[[1], [2], [3]]]))
created an array from object
[[[ 1.]
  [ 1.]
  [ 2.]]]
>>>
>>> # Creating arrays with column major data storage order:
 ...
>>> s = asfortranarray([[1, 2, 3], [4, 5, 6]])
>>> s.flags.f_contiguous
True
>>> print(s)
[[1 2 3]
 [4 5 6]]
>>> print(arr.foo(s))
>>> s2 = asfortranarray(s)
>>> s2 is s    # an array with column major storage order 
               # is returned immediately
True
>>> # Note that arr.foo returns a column major data storage order array:
 ...
>>> s3 = ascontiguousarray(s)
>>> s3.flags.f_contiguous
False
>>> s3.flags.c_contiguous
True
>>> s3 = arr.foo(s3)
copied an array: size=6, elsize=8
>>> s3.flags.f_contiguous
True
>>> s3.flags.c_contiguous
False

回呼引數#

F2PY 支援從 Fortran 或 C 程式碼呼叫 Python 函數。

考慮以下 Fortran 77 程式碼

C FILE: CALLBACK.F
      SUBROUTINE FOO(FUN,R)
      EXTERNAL FUN
      INTEGER I
      REAL*8 R, FUN
Cf2py intent(out) r
      R = 0D0
      DO I=-5,5
         R = R + FUN(I)
      ENDDO
      END
C END OF FILE CALLBACK.F

並使用 f2py -c -m callback callback.f 包裝它。

在 Python 中

>>> import callback
>>> print(callback.foo.__doc__)
r = foo(fun,[fun_extra_args])

Wrapper for ``foo``.

Parameters
----------
fun : call-back function

Other Parameters
----------------
fun_extra_args : input tuple, optional
    Default: ()

Returns
-------
r : float

Notes
-----
Call-back functions::

  def fun(i): return r
  Required arguments:
    i : input int
  Return objects:
    r : float

>>> def f(i): return i*i
... 
>>> print(callback.foo(f))
110.0
>>> print(callback.foo(lambda i:1))
11.0

在上面的範例中,F2PY 能夠準確地猜測回呼函數的簽名。但是,有時 F2PY 無法建立適當的簽名;在這些情況下,回呼函數的簽名必須在簽名檔中明確定義。

為了方便起見,簽名檔可能包含特殊模組(這些模組的名稱包含特殊的 __user__ 子字串),這些模組定義了回呼函數的各種簽名。常式簽名中的回呼引數具有 external 屬性(另請參閱 intent(callback) 屬性)。若要將回呼引數與 __user__ 模組區塊中的簽名關聯起來,可以使用 use 陳述式,如下所示。回呼引數的相同簽名可以在不同的常式簽名中引用。

我們使用與上一個範例中相同的 Fortran 77 程式碼,但現在我們將假裝 F2PY 無法正確猜測回呼引數的簽名。首先,我們使用 F2PY 建立初始簽名檔 callback2.pyf

f2py -m callback2 -h callback2.pyf callback.f

然後按如下方式修改它

!    -*- f90 -*-
python module __user__routines 
    interface
        function fun(i) result (r)
            integer :: i
            real*8 :: r
        end function fun
    end interface
end python module __user__routines

python module callback2
    interface
        subroutine foo(f,r)
            use __user__routines, f=>fun
            external f
            real*8 intent(out) :: r
        end subroutine foo
    end interface 
end python module callback2

最後,我們使用 f2py -c callback2.pyf callback.f 建置擴充模組。

此程式碼片段的 Python 會話範例與上一個範例相同,只是引數名稱會有所不同。

有時,Fortran 套件可能要求使用者提供套件將使用的常式。F2PY 可以建構此類常式的介面,以便可以從 Fortran 呼叫 Python 函數。

考慮以下 Fortran 77 子常式,它將陣列作為輸入,並將函數 func 應用於其元素。

      subroutine calculate(x,n)
cf2py intent(callback) func
      external func
c     The following lines define the signature of func for F2PY:
cf2py real*8 y
cf2py y = func(y)
c
cf2py intent(in,out,copy) x
      integer n,i
      real*8 x(n), func
      do i=1,n
         x(i) = func(x(i))
      end do
      end

Fortran 程式碼預期函數 func 已在外部定義。為了將 Python 函數用於 func,它必須具有屬性 intent(callback),並且必須在 external 陳述式之前指定。

最後,使用 f2py -c -m foo calculate.f 建置擴充模組

在 Python 中

>>> import foo
>>> foo.calculate(range(5), lambda x: x*x)
array([  0.,   1.,   4.,   9.,  16.])
>>> import math
>>> foo.calculate(range(5), math.exp)
array([  1.        ,   2.71828183,   7.3890561,  20.08553692,  54.59815003])

即使函數不在 Fortran 子常式引數列表中,該函數也作為引數包含在對 Fortran 子常式的 python 函數呼叫中。“external”關鍵字指的是 f2py 產生的 C 函數,而不是 Python 函數本身。python 函數本質上是提供給 C 函數的。

也可以在模組中明確設定回呼函數。然後,不必在引數列表中將函數傳遞給 Fortran 函數。如果呼叫 Python 回呼函數的 Fortran 函數本身由另一個 Fortran 函數呼叫,則可能需要這樣做。

考慮以下 Fortran 77 子常式

      subroutine f1()
         print *, "in f1, calling f2 twice.."
         call f2()
         call f2()
         return
      end
      
      subroutine f2()
cf2py    intent(callback, hide) fpy
         external fpy
         print *, "in f2, calling f2py.."
         call fpy()
         return
      end

並使用 f2py -c -m pfromf extcallback.f 包裝它。

在 Python 中

>>> import pfromf
>>> pfromf.f2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
pfromf.error: Callback fpy not defined (as an argument or module pfromf attribute).

>>> def f(): print("python f")
... 
>>> pfromf.fpy = f
>>> pfromf.f2()
 in f2, calling f2py..
python f
>>> pfromf.f1()
 in f1, calling f2 twice..
 in f2, calling f2py..
python f
 in f2, calling f2py..
python f
>>> 

注意

當透過 callstatement 或其他指示詞使用修改後的 Fortran 程式碼時,包裝的 Python 函數必須作為回呼呼叫,否則只會使用裸 Fortran 常式。如需更多詳細資訊,請參閱 numpy/numpy#26681

解析回呼函數的引數#

F2PY 產生的介面對回呼引數非常靈活。對於每個回呼引數,F2PY 都會引入一個額外的可選引數 <name>_extra_args。此引數可用於將額外引數傳遞給使用者提供的回呼函數。

如果 F2PY 產生的包裝函數預期以下回呼引數

def fun(a_1,...,a_n):
   ...
   return x_1,...,x_k

但使用者提供了以下 Python 函數

def gun(b_1,...,b_m):
   ...
   return y_1,...,y_l

此外,

fun_extra_args = (e_1,...,e_p)

被使用,則當 Fortran 或 C 函數評估回呼引數 gun 時,會套用以下規則

  • 如果 p == 0,則會呼叫 gun(a_1, ..., a_q),其中 q = min(m, n)

  • 如果 n + p <= m,則會呼叫 gun(a_1, ..., a_n, e_1, ..., e_p)

  • 如果 p <= m < n + p,則會呼叫 gun(a_1, ..., a_q, e_1, ..., e_p),此處 q=m-p

  • 如果 p > m,則會呼叫 gun(e_1, ..., e_m)

  • 如果 n + p 小於 gun 所需的引數數量,則會引發例外狀況。

如果函數 gun 可能會傳回任意數量的物件作為元組;則會套用以下規則

  • 如果 k < l,則會忽略 y_{k + 1}, ..., y_l

  • 如果 k > l,則僅會設定 x_1, ..., x_l

Common blocks#

F2PY 為常式簽名區塊中定義的 common blocks 產生包裝器。Common blocks 對連結到目前擴充模組的所有 Fortran 程式碼都可見,但對其他擴充模組不可見(此限制是由於 Python 匯入共用程式庫的方式所致)。在 Python 中,F2PY 為 common blocks 產生的包裝器是 fortran 類型物件,它們具有與 common blocks 的資料成員相關的(動態)屬性。存取這些屬性時,它們會以 NumPy 陣列物件(多維陣列是 Fortran 連續的)的形式傳回,這些物件直接連結到 common blocks 中的資料成員。可以透過直接賦值或對應陣列物件的就地變更來變更資料成員。

考慮以下 Fortran 77 程式碼

C FILE: COMMON.F
      SUBROUTINE FOO
      INTEGER I,X
      REAL A
      COMMON /DATA/ I,X(4),A(2,3)
      PRINT*, "I=",I
      PRINT*, "X=[",X,"]"
      PRINT*, "A=["
      PRINT*, "[",A(1,1),",",A(1,2),",",A(1,3),"]"
      PRINT*, "[",A(2,1),",",A(2,2),",",A(2,3),"]"
      PRINT*, "]"
      END
C END OF COMMON.F

並使用 f2py -c -m common common.f 包裝它。

在 Python 中

>>> import common
>>> print(common.data.__doc__)
i : 'i'-scalar
x : 'i'-array(4)
a : 'f'-array(2,3)

>>> common.data.i = 5
>>> common.data.x[1] = 2 
>>> common.data.a = [[1,2,3],[4,5,6]]
>>> common.foo()
>>> common.foo()
 I=           5
 X=[           0           2           0           0 ]
 A=[
 [   1.00000000     ,   2.00000000     ,   3.00000000     ]
 [   4.00000000     ,   5.00000000     ,   6.00000000     ]
 ]
>>> common.data.a[1] = 45
>>> common.foo()
 I=           5
 X=[           0           2           0           0 ]
 A=[
 [   1.00000000     ,   2.00000000     ,   3.00000000     ]
 [   45.0000000     ,   45.0000000     ,   45.0000000     ]
 ]
>>> common.data.a                 # a is Fortran-contiguous
array([[  1.,   2.,   3.],
       [ 45.,  45.,  45.]], dtype=float32)
>>> common.data.a.flags.f_contiguous
True

Fortran 90 模組資料#

F2PY 對 Fortran 90 模組資料的介面與處理 Fortran 77 common blocks 類似。

考慮以下 Fortran 90 程式碼

module mod
  integer i
  integer :: x(4)
  real, dimension(2,3) :: a
  real, allocatable, dimension(:,:) :: b 
contains
  subroutine foo
    integer k
    print*, "i=",i
    print*, "x=[",x,"]"
    print*, "a=["
    print*, "[",a(1,1),",",a(1,2),",",a(1,3),"]"
    print*, "[",a(2,1),",",a(2,2),",",a(2,3),"]"
    print*, "]"
    print*, "Setting a(1,2)=a(1,2)+3"
    a(1,2) = a(1,2)+3
  end subroutine foo
end module mod

並使用 f2py -c -m moddata moddata.f90 包裝它。

在 Python 中

>>> import moddata
>>> print(moddata.mod.__doc__)
i : 'i'-scalar
x : 'i'-array(4)
a : 'f'-array(2,3)
b : 'f'-array(-1,-1), not allocated
foo()

Wrapper for ``foo``.



>>> moddata.mod.i = 5  
>>> moddata.mod.x[:2] = [1,2]
>>> moddata.mod.a = [[1,2,3],[4,5,6]]
>>> moddata.mod.foo()                
 i=           5
 x=[           1           2           0           0 ]
 a=[
 [   1.000000     ,   2.000000     ,   3.000000     ]
 [   4.000000     ,   5.000000     ,   6.000000     ]
 ]
 Setting a(1,2)=a(1,2)+3
>>> moddata.mod.a               # a is Fortran-contiguous
array([[ 1.,  5.,  3.],
       [ 4.,  5.,  6.]], dtype=float32)
>>> moddata.mod.a.flags.f_contiguous
True

可配置陣列#

F2PY 對 Fortran 90 模組可配置陣列具有基本支援。

考慮以下 Fortran 90 程式碼

module mod
  real, allocatable, dimension(:,:) :: b 
contains
  subroutine foo
    integer k
    if (allocated(b)) then
       print*, "b=["
       do k = 1,size(b,1)
          print*, b(k,1:size(b,2))
       enddo
       print*, "]"
    else
       print*, "b is not allocated"
    endif
  end subroutine foo
end module mod

並使用 f2py -c -m allocarr allocarr.f90 包裝它。

在 Python 中

>>> import allocarr
>>> print(allocarr.mod.__doc__)
b : 'f'-array(-1,-1), not allocated
foo()

Wrapper for ``foo``.



>>> allocarr.mod.foo()  
 b is not allocated
>>> allocarr.mod.b = [[1, 2, 3], [4, 5, 6]]             # allocate/initialize b
>>> allocarr.mod.foo()
 b=[
   1.000000       2.000000       3.000000    
   4.000000       5.000000       6.000000    
 ]
>>> allocarr.mod.b                                      # b is Fortran-contiguous
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]], dtype=float32)
>>> allocarr.mod.b.flags.f_contiguous
True
>>> allocarr.mod.b = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]  # reallocate/initialize b
>>> allocarr.mod.foo()
 b=[
   1.000000       2.000000       3.000000    
   4.000000       5.000000       6.000000    
   7.000000       8.000000       9.000000    
 ]
>>> allocarr.mod.b = None                               # deallocate array
>>> allocarr.mod.foo()
 b is not allocated