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 包示例#

下面是一个最小的 setup.py 文件示例,用于纯 SciPy 包:

#!/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(..) 函数期望的关键字相同,例如 packages , ext_modules , data_files , include_dirs , libraries , headers , scripts , package_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 `` 的 `` paths `` 项.

  • `` config.get_subpackage(subpackage_name,subpackage_path=None) `` — 返回子包配置的列表. 将在当前目录下的 `` subpackage_name `` 名称下查找子包,但也可以通过可选的 `` subpackage_path `` 参数指定路径. 如果将 `` subpackage_name `` 指定为 `` None ``,则子包名称将取 `` subpackage_path `` 的 basename. 用于子包名称的任何 `` * `` 都将扩展为通配符.

  • `` 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 `` 的 basename 下. 例如,

    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_dirs , define_macros , undef_macros , library_dirs , libraries , runtime_library_dirs , extra_objects , extra_compile_args , extra_link_args , export_symbols , swig_opts , depends , language , f2py_options , module_dirs , extra_info , extra_f77_compile_args , extra_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 实例,该实例可用于访问其属性,例如 depends , sources 等列表,并在构建过程中修改它们.第二个参数给出了一个构建目录的路径,该路径必须在创建磁盘文件时使用.

  • config.add_library(name, sources, build_info) — 将库添加到 libraries 列表.允许的关键字参数是 depends , macros , include_dirs , extra_compiler_args , f2py_options , extra_f77_compile_args , extra_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.py , version.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.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 文件#

此模板转换器将根据 ‘<…>’ 中的规则复制文件中所有包含 ‘<…>’ 的函数和子程序块.’<’…’>’ 中逗号分隔的单词的数量决定了块重复的次数. 这些单词表示该重复规则 ‘<…>’ 在每个块中应该被替换成的内容.块中的所有重复规则必须包含相同数量的逗号分隔的单词,表示该块应该重复的次数. 如果重复规则中的单词需要逗号,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. 在指定命名变量的重复规则时, itemNitem, 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@ 变量,目标是字符串替换,它被预处理为多个其他相同的循环,其中包含多个字符串,如 INT , LONG , UINT , ULONG . 因此, @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基本include目录的列表.NumPy基本include目录包含头文件,例如 numpy/arrayobject.h , numpy/funcobject.h 等.对于已安装的NumPy,返回的列表长度为1,但是当构建NumPy时,该列表可能包含更多目录,例如,到 config.h 文件的路径,该文件由 numpy/base/setup.py 文件生成,并被 numpy 头文件使用.

  • 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_match , fortran_ext_match , f90_ext_match , f90_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 行中,并使用指定类型的 fcompiler 的 f77flags (第一个字符 C 是可选的).

TODO:此功能可以轻松扩展到 Fortran 90 代码.如果您需要这样的功能,请告诉我们.