三種封裝方法 - 入門#
使用 F2PY 將 Fortran 或 C 函數封裝到 Python 包含以下步驟
建立所謂的簽名檔,其中包含 Fortran 或 C 函數的封裝器描述,也稱為函數的簽名。對於 Fortran 常式,F2PY 可以通過掃描 Fortran 原始碼並追蹤建立封裝函數所需的所有相關資訊來建立初始簽名檔。
可選地,可以編輯 F2PY 建立的簽名檔以優化封裝函數,這可以使它們更「聰明」和更「Python 化」。
F2PY 讀取簽名檔並寫入包含 Fortran/C/Python 綁定的 Python C/API 模組。
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 "<stdin>", line 1, in <module> fib.error: (len(a)>=n) failed for 1st keyword n: fib:n=10 >>>
F2PY 實作相關引數之間的基本相容性檢查,以避免意外崩潰。
當 NumPy 陣列是 Fortran 連續 且具有與假定的 Fortran 類型相對應的
dtype
時,將其用作輸入陣列引數,則其 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)
屬性。這種方法更有效率,也更簡潔。在 Python 中使用
fib1.fib
與在 Fortran 中使用FIB
非常相似。但是,在 Python 中使用就地輸出引數是一種不良風格,因為 Python 中沒有安全機制來防止錯誤的引數類型。使用 Fortran 或 C 時,編譯器會在編譯過程中發現任何類型不符的情況,但在 Python 中,必須在執行階段檢查類型。因此,在 Python 中使用就地輸出引數可能會導致難以發現的錯誤,更不用說當實作所有必需的類型檢查時,程式碼的可讀性會降低。
儘管到目前為止討論的用於封裝 Python 的 Fortran 常式的方法非常簡單,但它有幾個缺點(請參閱上面的註解)。缺點是由於 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)
屬性),並且結果,即在呼叫 Fortran 函數FIB
之後a
的內容,應傳回給 Python(使用intent(out)
屬性)。此外,應使用由輸入引數n
確定的大小動態建立陣列a
(使用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
的簽名現在更符合 Fortran 副程式FIB
的意圖:給定數字n
,fib2.fib
以 NumPy 陣列的形式傳回前n
個費波那契數。新的 Python 簽名fib2.fib
也排除了fib1.fib
中意外的行為。請注意,預設情況下,使用單個
intent(out)
也意味著intent(hide)
。指定了intent(hide)
屬性的引數將不會列在封裝函數的引數列表中。
有關更多詳細資訊,請參閱簽名檔。
快速且聰明的方法#
如上所述,「聰明的方法」封裝 Fortran 函數適用於封裝(例如,第三方)Fortran 程式碼,這些程式碼不希望甚至不可能對其原始碼進行修改。
但是,如果可以接受編輯 Fortran 程式碼,則在大多數情況下可以跳過產生中間簽名檔。可以使用 F2PY 指令將 F2PY 特定屬性直接插入到 Fortran 原始碼中。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.]