使用 meson#

注意

本文件的大部分內容現已過時,可以執行帶有 --build-dirf2py,以取得具有基本相依性設定的骨架 meson 專案。

在 1.26.x 版本中變更:f2py 的預設建置系統現在是 meson,如需更多詳細資訊,請參閱 numpy.distutils 的狀態與遷移建議

藉由利用 meson 相較於 使用 numpy.distutils 中描述的技術所獲得的主要優勢在於,它可以輕鬆地應用於現有系統和更大的專案。meson 具有相當 Python 風格的語法,這使得 python 使用者更容易上手並適合擴充。

Fibonacci 逐步解說 (F77)#

在我們可以使用像 meson 這樣的通用建置系統之前,我們需要產生的 C 包裝器。我們將透過以下方式取得:

python -m numpy.f2py fib1.f -m fib2

現在,考慮以下來自 三種包裝方式 - 入門 區段的 fibscalar 範例的 meson.build 檔案

project('f2py_examples', 'c',
  version : '0.1',
  license: 'BSD-3',
  meson_version: '>=0.64.0',
  default_options : ['warning_level=2'],
)

add_languages('fortran')

py_mod = import('python')
py = py_mod.find_installation(pure: false)
py_dep = py.dependency()

incdir_numpy = run_command(py,
  ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
  check : true
).stdout().strip()

incdir_f2py = run_command(py,
    ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'],
    check : true
).stdout().strip()

inc_np = include_directories(incdir_numpy, incdir_f2py)

py.extension_module('fib2',
  [
    'fib1.f',
    'fib2module.c',  # note: this assumes f2py was manually run before!
  ],
  incdir_f2py / 'fortranobject.c',
  include_directories: inc_np,
  dependencies : py_dep,
  install : true
)

在此時,建置將完成,但匯入將失敗

meson setup builddir
meson compile -C builddir
cd builddir
python -c 'import fib2'
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: fib2.cpython-39-x86_64-linux-gnu.so: undefined symbol: FIB_
# Check this isn't a false positive
nm -A fib2.cpython-39-x86_64-linux-gnu.so | grep FIB_
fib2.cpython-39-x86_64-linux-gnu.so: U FIB_

回想一下,如下所示的原始範例是以 SCREAMCASE 格式呈現

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

使用標準方法,暴露給 python 的副程式是 fib 而不是 FIB。這表示我們有幾個選項。一種方法(在可能的情況下)是使用以下命令將原始 Fortran 檔案轉換為小寫:

tr "[:upper:]" "[:lower:]" < fib1.f > fib1.f
python -m numpy.f2py fib1.f -m fib2
meson --wipe builddir
meson compile -C builddir
cd builddir
python -c 'import fib2'

然而,這需要修改原始碼的能力,但這並非總是可行。解決此問題最簡單的方法是讓 f2py 處理它

python -m numpy.f2py fib1.f -m fib2 --lower
meson --wipe builddir
meson compile -C builddir
cd builddir
python -c 'import fib2'

自動化包裝器產生#

上述工作流程中的一個主要痛點是手動追蹤輸入。雖然由於 F2PY 與建置系統 中討論的原因,需要更多努力才能弄清楚實際輸出。

注意

從 NumPy 1.22.4 開始,f2py 將根據輸入檔案 Fortran 標準(F77 或更高版本)確定性地產生包裝器檔案。--skip-empty-wrappers 可以傳遞給 f2py,以恢復先前僅在輸入需要時才產生包裝器的行為。

然而,我們可以以直接的方式擴增我們的工作流程,以考量在設定建置系統時已知輸出的檔案。

project('f2py_examples', 'c',
  version : '0.1',
  license: 'BSD-3',
  meson_version: '>=0.64.0',
  default_options : ['warning_level=2'],
)

add_languages('fortran')

py_mod = import('python')
py = py_mod.find_installation(pure: false)
py_dep = py.dependency()

incdir_numpy = run_command(py,
  ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'],
  check : true
).stdout().strip()

incdir_f2py = run_command(py,
    ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'],
    check : true
).stdout().strip()

fibby_source = custom_target('fibbymodule.c',
  input : ['fib1.f'],  # .f so no F90 wrappers
  output : ['fibbymodule.c', 'fibby-f2pywrappers.f'],
  command : [py, '-m', 'numpy.f2py', '@INPUT@', '-m', 'fibby', '--lower']
)

inc_np = include_directories(incdir_numpy, incdir_f2py)

py.extension_module('fibby',
  ['fib1.f', fibby_source],
  incdir_f2py / 'fortranobject.c',
  include_directories: inc_np,
  dependencies : py_dep,
  install : true
)

這可以像以前一樣編譯和執行。

rm -rf builddir
meson setup builddir
meson compile -C builddir
cd builddir
python -c "import numpy as np; import fibby; a = np.zeros(9); fibby.fib(a); print (a)"
# [ 0.  1.  1.  2.  3.  5.  8. 13. 21.]

要點#

值得記住以下幾點

  • 在此情境中無法使用 SCREAMCASE,因此 .f 檔案的內容或產生的包裝器 .c 需要轉換為小寫字母;這可以透過 F2PY--lower 選項來實現