numpy.distutils 使用者指南#

警告

numpy.distutils 已棄用,將在 Python >= 3.12 中移除。如需更多詳細資訊,請參閱 numpy.distutils 的狀態和遷移建議

SciPy 結構#

目前 SciPy 專案由兩個套件組成

  • NumPy — 它提供像這樣的套件

    • numpy.distutils - Python distutils 的擴充

    • numpy.f2py - 一個將 Fortran/C 程式碼綁定到 Python 的工具

    • numpy._core - Numeric 和 numarray 套件的未來替代品

    • numpy.lib - 額外實用工具函數

    • numpy.testing - 用於單元測試的 numpy 風格工具

    • 等等

  • SciPy — 用於 Python 的科學工具集合。

本文檔的目的是描述如何將新工具添加到 SciPy。

SciPy 套件的需求#

SciPy 由 Python 套件組成,稱為 SciPy 套件,Python 使用者可透過 scipy 命名空間使用這些套件。每個 SciPy 套件可能包含其他 SciPy 套件。依此類推。因此,SciPy 目錄樹是一個具有任意深度和寬度的套件樹。任何 SciPy 套件可能依賴 NumPy 套件,但對其他 SciPy 套件的依賴性應保持最小或為零。

一個 SciPy 套件除了其原始碼外,還包含以下檔案和目錄

  • setup.py — 建置腳本

  • __init__.py — 套件初始化器

  • tests/ — 單元測試目錄

其內容如下所述。

setup.py 檔案#

為了將 Python 套件添加到 SciPy,其建置腳本 (setup.py) 必須滿足某些需求。最重要的需求是套件必須定義一個 configuration(parent_package='',top_path=None) 函數,該函數傳回一個適合傳遞給 numpy.distutils.core.setup(..) 的字典。為了簡化此字典的建構,numpy.distutils.misc_util 提供了 Configuration 類別,如下所述。

SciPy 純 Python 套件範例#

以下是一個純 SciPy 套件的最小 setup.py 檔案範例

#!/usr/bin/env python3
def configuration(parent_package='',top_path=None):
    from numpy.distutils.misc_util import Configuration
    config = Configuration('mypackage',parent_package,top_path)
    return config

if __name__ == "__main__":
    from numpy.distutils.core import setup
    #setup(**configuration(top_path='').todict())
    setup(configuration=configuration)

configuration 函數的參數指定了父 SciPy 套件的名稱 (parent_package) 和主要 setup.py 腳本的目錄位置 (top_path)。這些參數以及目前套件的名稱應傳遞給 Configuration 建構子。

Configuration 建構子具有第四個可選參數 package_path,當套件檔案位於與 setup.py 檔案目錄不同的位置時可以使用該參數。

剩餘的 Configuration 參數都是關鍵字參數,這些參數將用於初始化 Configuration 實例的屬性。通常,這些關鍵字與 setup(..) 函數預期的關鍵字相同,例如 packagesext_modulesdata_filesinclude_dirslibrariesheadersscriptspackage_dir 等。但是,不建議直接指定這些關鍵字,因為這些關鍵字參數的內容將不會被處理或檢查 SciPy 建置系統的一致性。

最後,Configuration 具有 .todict() 方法,該方法傳回所有配置資料作為一個適合傳遞給 setup(..) 函數的字典。

Configuration 實例屬性#

除了可以透過關鍵字參數指定給 Configuration 建構子的屬性之外,Configuration 實例(讓我們表示為 config)還具有以下屬性,這些屬性在編寫 setup 腳本時可能很有用

  • config.name - 目前套件的完整名稱。父套件的名稱可以提取為 config.name.split('.')

  • config.local_path - 目前 setup.py 檔案位置的路徑。

  • config.top_path - 主要 setup.py 檔案位置的路徑。

Configuration 實例方法#

  • config.todict() — 傳回適合傳遞給 numpy.distutils.core.setup(..) 函數的配置字典。

  • config.paths(*paths) --- 必要時將 ``glob.glob(..)`` 應用於 paths 的項目。修正相對於 config.local_path 的路徑項目。

  • config.get_subpackage(subpackage_name,subpackage_path=None) — 傳回子套件配置的列表。子套件在目前目錄下以名稱 subpackage_name 查找,但也可以透過可選的 subpackage_path 參數指定路徑。如果 subpackage_name 指定為 None,則子套件名稱將取自 subpackage_path 的基本名稱。用於子套件名稱的任何 * 都會展開為萬用字元。

  • config.add_subpackage(subpackage_name,subpackage_path=None) — 將 SciPy 子套件配置新增至目前的配置。參數的含義和用法如上所述,請參閱 config.get_subpackage() 方法。

  • config.add_data_files(*files) — 將 files 前置到 data_files 列表。如果 files 項目是一個元組,則其第一個元素定義資料檔案複製位置的後綴(相對於套件安裝目錄),第二個元素指定資料檔案的路徑。預設情況下,資料檔案會複製到套件安裝目錄下。例如,

    config.add_data_files('foo.dat',
                          ('fun',['gun.dat','nun/pun.dat','/tmp/sun.dat']),
                          'bar/car.dat'.
                          '/full/path/to/can.dat',
                          )
    

    將資料檔案安裝到以下位置

    <installation path of config.name package>/
      foo.dat
      fun/
        gun.dat
        pun.dat
        sun.dat
      bar/
        car.dat
      can.dat
    

    資料檔案的路徑可以是一個不帶參數並傳回資料檔案路徑(或路徑列表)的函數 – 當在建置套件時產生資料檔案時,這非常有用。(XXX: 解釋確切呼叫此函數的步驟)

  • config.add_data_dir(data_path) — 將目錄 data_path 遞迴新增至 data_files。從 data_path 開始的整個目錄樹將複製到套件安裝目錄下。如果 data_path 是一個元組,則其第一個元素定義資料檔案複製位置的後綴(相對於套件安裝目錄),第二個元素指定資料目錄的路徑。預設情況下,資料目錄會複製到套件安裝目錄下,並以 data_path 的基本名稱命名。例如,

    config.add_data_dir('fun')  # fun/ contains foo.dat bar/car.dat
    config.add_data_dir(('sun','fun'))
    config.add_data_dir(('gun','/full/path/to/fun'))
    

    將資料檔案安裝到以下位置

    <installation path of config.name package>/
      fun/
         foo.dat
         bar/
            car.dat
      sun/
         foo.dat
         bar/
            car.dat
      gun/
         foo.dat
         bar/
            car.dat
    
  • config.add_include_dirs(*paths) — 將 paths 前置到 include_dirs 列表。此列表對目前套件的所有擴充模組可見。

  • config.add_headers(*files) — 將 files 前置到 headers 列表。預設情況下,標頭檔將安裝在 <prefix>/include/pythonX.X/<config.name.replace('.','/')>/ 目錄下。如果 files 項目是一個元組,則其第一個參數指定相對於 <prefix>/include/pythonX.X/ 路徑的安裝後綴。這是 Python distutils 方法;不建議 NumPy 和 SciPy 使用此方法,而建議使用 config.add_data_files(*files)

  • config.add_scripts(*files) — 將 files 前置到 scripts 列表。腳本將安裝在 <prefix>/bin/ 目錄下。

  • config.add_extension(name,sources,**kw) — 建立並將 Extension 實例新增至 ext_modules 列表。第一個參數 name 定義擴充模組的名稱,該模組將安裝在 config.name 套件下。第二個參數是原始碼列表。add_extension 方法也接受傳遞給 Extension 建構子的關鍵字參數。允許的關鍵字列表如下:include_dirsdefine_macrosundef_macroslibrary_dirslibrariesruntime_library_dirsextra_objectsextra_compile_argsextra_link_argsexport_symbolsswig_optsdependslanguagef2py_optionsmodule_dirsextra_infoextra_f77_compile_argsextra_f90_compile_args

    請注意,config.paths 方法適用於所有可能包含路徑的列表。extra_info 是一個字典或字典列表,其內容將附加到關鍵字參數。列表 depends 包含擴充模組原始碼所依賴的檔案或目錄路徑。如果 depends 列表中有任何路徑比擴充模組新,則將重建該模組。

    原始碼列表可能包含函數(「原始碼產生器」),模式為 def <funcname>(ext, build_dir): return <source(s) or None>。如果 funcname 傳回 None,則不產生任何原始碼。如果 Extension 實例在處理所有原始碼產生器後沒有原始碼,則不會建置任何擴充模組。這是用於有條件地定義擴充模組的建議方法。原始碼產生器函數由 numpy.distutilsbuild_src 子命令呼叫。

    例如,這是一個典型的原始碼產生器函數

    def generate_source(ext,build_dir):
        import os
        from distutils.dep_util import newer
        target = os.path.join(build_dir,'somesource.c')
        if newer(target,__file__):
            # create target file
        return target
    

    第一個參數包含 Extension 實例,可用於存取其屬性,例如 dependssources 等列表,並在建置過程中修改它們。第二個參數提供建置目錄的路徑,在建立檔案到磁碟時必須使用該路徑。

  • config.add_library(name, sources, **build_info) — 將程式庫新增至 libraries 列表。允許的關鍵字參數為 dependsmacrosinclude_dirsextra_compiler_argsf2py_optionsextra_f77_compile_argsextra_f90_compile_args。有關參數的更多資訊,請參閱 .add_extension() 方法。

  • config.have_f77c() — 如果 Fortran 77 編譯器可用,則傳回 True(讀作:簡單的 Fortran 77 程式碼編譯成功)。

  • config.have_f90c() — 如果 Fortran 90 編譯器可用,則傳回 True(讀作:簡單的 Fortran 90 程式碼編譯成功)。

  • config.get_version() — 傳回目前套件的版本字串;如果無法偵測到版本資訊,則傳回 None。此方法掃描檔案 __version__.py<packagename>_version.pyversion.py__svn_version__.py 以尋找字串變數 version__version__<packagename>_version

  • config.make_svn_version_py() — 將資料函數附加到 data_files 列表,該函數將在目前套件目錄中產生 __svn_version__.py 檔案。當 Python 結束時,該檔案將從原始碼目錄中移除。

  • config.get_build_temp_dir() — 傳回暫存目錄的路徑。這是應該建置暫存檔案的地方。

  • config.get_distribution() — 傳回 distutils Distribution 實例。

  • config.get_config_cmd() — 傳回 numpy.distutils config 命令實例。

  • config.get_info(*names)

使用範本轉換 .src 檔案#

NumPy distutils 支援自動轉換名為 <somefile>.src 的原始碼檔案。此功能可用於維護非常相似的程式碼區塊,這些區塊之間只需要簡單的變更。在 setup 的建置階段期間,如果遇到名為 <somefile>.src 的範本檔案,則將從範本建構名為 <somefile> 的新檔案,並將其放置在建置目錄中使用。支援兩種形式的範本轉換。第一種形式發生在名為 <file>.ext.src 的檔案中,其中 ext 是公認的 Fortran 擴充(f、f90、f95、f77、for、ftn、pyf)。第二種形式用於所有其他情況。

Fortran 檔案#

此範本轉換器將根據「<…>」中的規則,複製檔案中名稱包含「<…>」的所有 functionsubroutine 區塊。「<…>」中逗號分隔的單字數決定區塊重複的次數。這些單字表示每個區塊中應將重複規則「<…>」替換為什麼。區塊中的所有重複規則都必須包含相同數量的逗號分隔單字,以指示該區塊應重複的次數。如果重複規則中的單字需要逗號、leftarrow 或 rightarrow,則在其前面加上反斜線「\」。如果重複規則中的單字與「<index>」匹配,則將其替換為相同重複規格中的第 <index> 個單字。重複規則有兩種形式:具名和簡短。

具名重複規則#

當同一組重複必須在區塊中多次使用時,具名重複規則非常有用。它使用 <rule1=item1, item2, item3,…, itemN> 指定,其中 N 是區塊應重複的次數。在區塊的每次重複中,整個表達式「<…>」將依序替換為 item1、item2,依此類推,直到完成 N 次重複。一旦引入具名重複規格,則可以在目前區塊中使用相同的重複規則,只需參考名稱即可(即 <rule1>)。

簡短重複規則#

簡短重複規則看起來像 <item1, item2, item3, …, itemN>。該規則指定整個表達式「<…>」應首先替換為 item1,然後替換為 item2,依此類推,直到完成 N 次重複。

預定義名稱#

提供以下預定義的具名重複規則

  • <prefix=s,d,c,z>

  • <_c=s,d,c,z>

  • <_t=real, double precision, complex, double complex>

  • <ftype=real, double precision, complex, double complex>

  • <ctype=float, double, complex_float, complex_double>

  • <ftypereal=float, double precision, \0, \1>

  • <ctypereal=float, double, \0, \1>

其他檔案#

非 Fortran 檔案使用單獨的語法來定義範本區塊,這些區塊應使用類似於 Fortran 特定重複的具名重複規則的變數展開來重複。

NumPy Distutils 預處理以自訂範本語言編寫的 C 原始碼檔案(副檔名:.c.src)以產生 C 程式碼。符號 @ 用於包裝巨集樣式的變數,以啟用可能描述(例如)一組資料類型的字串替換機制。

範本語言區塊以 /**begin repeat/**end repeat**/ 行分隔,這些行也可以使用連續編號的分隔行(例如 /**begin repeat1/**end repeat1**/)巢狀

  1. 單獨一行上的 /**begin repeat 標記應重複的區段的開頭。

  2. 具名變數展開使用 #name=item1, item2, item3, ..., itemN# 定義,並放置在連續的行上。這些變數在每個重複區塊中都替換為對應的單字。同一重複區塊中的所有具名變數都必須定義相同數量的單字。

  3. 在指定具名變數的重複規則時,item*Nitem, item, ..., item 重複 N 次的簡寫。此外,括號與 *N 結合使用,可用於將應重複的幾個項目分組。因此,#name=(item1, item2)*4# 等同於 #name=item1, item2, item1, item2, item1, item2, item1, item2#

  4. 單獨一行上的 */ 標記變數展開命名的結尾。下一行是將使用具名規則重複的第一行。

  5. 在要重複的區塊內,應展開的變數指定為 @name@

  6. 單獨一行上的 /**end repeat**/ 標記前一行作為要重複的區塊的最後一行。

  7. NumPy C 原始碼中的迴圈可能包含用於字串替換的 @TYPE@ 變數,它會被預處理成多個相似的迴圈,以處理如 INTLONGUINTULONG 等字串。@TYPE@ 樣式語法因此透過模仿具有泛型型別支援的語言來減少程式碼重複和維護負擔。

以上規則在以下範本原始碼範例中可能更清楚

 1 /* TIMEDELTA to non-float types */
 2
 3 /**begin repeat
 4  *
 5  * #TOTYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG,
 6  *           LONGLONG, ULONGLONG, DATETIME,
 7  *           TIMEDELTA#
 8  * #totype = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 9  *           npy_long, npy_ulong, npy_longlong, npy_ulonglong,
10  *           npy_datetime, npy_timedelta#
11  */
12
13 /**begin repeat1
14  *
15  * #FROMTYPE = TIMEDELTA#
16  * #fromtype = npy_timedelta#
17  */
18 static void
19 @FROMTYPE@_to_@TOTYPE@(void *input, void *output, npy_intp n,
20         void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
21 {
22     const @fromtype@ *ip = input;
23     @totype@ *op = output;
24
25     while (n--) {
26         *op++ = (@totype@)*ip++;
27     }
28 }
29 /**end repeat1**/
30
31 /**end repeat**/

泛型型別 C 原始碼檔案的預處理(無論是在 NumPy 本身中還是在任何使用 NumPy Distutils 的第三方套件中)都由 conv_template.py 執行。這些模組在建置過程中產生的型別特定 C 檔案(副檔名:.c)已準備好進行編譯。C 標頭檔也支援這種形式的泛型型別(預處理以產生 .h 檔案)。

numpy.distutils.misc_util 中的實用函數#

  • get_numpy_include_dirs() — 傳回 NumPy 基礎包含目錄的列表。NumPy 基礎包含目錄包含標頭檔,例如 numpy/arrayobject.hnumpy/funcobject.h 等。對於已安裝的 NumPy,傳回的列表長度為 1,但在建置 NumPy 時,該列表可能包含更多目錄,例如,numpy/base/setup.py 檔案產生並由 numpy 標頭檔使用的 config.h 檔案的路徑。

  • append_path(prefix,path) — 將 path 智慧型附加到 prefix

  • gpaths(paths, local_path='') — 將 glob 應用於路徑,並在需要時前置 local_path

  • njoin(*path) — 聯結路徑名稱組件 + 將 /-分隔的路徑轉換為 os.sep-分隔的路徑,並從路徑中解析 ...。例如 njoin('a',['b','./c'],'..','g') -> os.path.join('a','b','g')

  • minrelpath(path) — 解析 path 中的點。

  • rel_path(path, parent_path) — 傳回相對於 parent_pathpath

  • def get_cmd(cmdname,_cache={}) — 傳回 numpy.distutils 命令實例。

  • all_strings(lst)

  • has_f_sources(sources)

  • has_cxx_sources(sources)

  • filter_sources(sources) — 傳回 c_sources, cxx_sources, f_sources, fmodule_sources

  • get_dependencies(sources)

  • is_local_src_dir(directory)

  • get_ext_source_files(ext)

  • get_script_files(scripts)

  • get_lib_source_files(lib)

  • get_data_files(data)

  • dot_join(*args) — 用點聯結非零參數。

  • get_frame(level=0) — 從具有給定層級的呼叫堆疊傳回框架物件。

  • cyg2win32(path)

  • mingw32() — 使用 mingw32 環境時傳回 True

  • terminal_has_colors()red_text(s)green_text(s)yellow_text(s)blue_text(s)cyan_text(s)

  • get_path(mod_name,parent_path=None) — 給定時傳回相對於 parent_path 的模組路徑。也處理 __main____builtin__ 模組。

  • allpath(name) — 將 name 中的 / 替換為 os.sep

  • cxx_ext_matchfortran_ext_matchf90_ext_matchf90_module_name_match

numpy.distutils.system_info 模組#

  • get_info(name,notfound_action=0)

  • combine_paths(*args,**kws)

  • show_all()

numpy.distutils.cpuinfo 模組#

  • cpuinfo

numpy.distutils.log 模組#

  • set_verbosity(v)

numpy.distutils.exec_command 模組#

  • get_pythonexe()

  • find_executable(exe, path=None)

  • exec_command( command, execute_in='', use_shell=None, use_tee=None, **env )

__init__.py 檔案#

典型的 SciPy __init__.py 的標頭為

"""
Package docstring, typically with a brief description and function listing.
"""

# import functions into module namespace
from .subpackage import *
...

__all__ = [s for s in dir() if not s.startswith('_')]

from numpy.testing import Tester
test = Tester().test
bench = Tester().bench

NumPy Distutils 中的額外功能#

在 setup.py 腳本中為程式庫指定 config_fc 選項#

可以在 setup.py 腳本中指定 config_fc 選項。例如,使用

config.add_library('library',
                   sources=[...],
                   config_fc={'noopt':(__file__,1)})

將編譯 library 原始碼,但不使用最佳化旗標。

建議以編譯器獨立的方式僅指定那些 config_fc 選項。

從原始碼取得額外的 Fortran 77 編譯器選項#

某些舊的 Fortran 程式碼需要特殊的編譯器選項才能正常運作。為了指定每個原始碼檔案的編譯器選項,numpy.distutils Fortran 編譯器會尋找以下模式

CF77FLAGS(<fcompiler type>) = <fcompiler f77flags>

在原始碼的前 20 行中,並將 f77flags 用於指定類型的 fcompiler(第一個字元 C 是可選的)。

TODO: 此功能也可以輕鬆擴展到 Fortran 90 程式碼。如果您需要此功能,請告知我們。