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 文件#

为了向 SciPy 添加 Python 包,其构建脚本 (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)还有以下属性,这些属性在编写安装脚本时可能很有用:

  • 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_pathpaths 项目。

  • 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) — 创建并向ext_modules列表添加一个Extension实例。第一个参数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实例,这对于访问其属性(如dependssources等列表)并在构建过程中修改它们非常有用。第二个参数给出构建目录的路径,在创建文件到磁盘时必须使用该路径。

  • 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 编译器可用(即:一个简单的 Fortran 77 代码成功编译),则返回 True。

  • config.have_f90c() — 如果 Fortran 90 编译器可用(即:一个简单的 Fortran 90 代码成功编译),则返回 True。

  • 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.get_info(*names)

使用模板转换 .src 文件#

NumPy distutils 支持自动转换名为 <somefile>.src 的源文件。此功能可用于维护非常相似的代码块,这些代码块只需要在块之间进行简单的更改。在安装程序的构建阶段,如果遇到名为 <somefile>.src 的模板文件,则将从模板构造一个名为 <somefile> 的新文件,并将其放置在构建目录中以代替它。支持两种形式的模板转换。第一种形式用于名为 <file>.ext.src 的文件,其中 ext 是识别的 Fortran 扩展名 (f、f90、f95、f77、for、ftn、pyf)。第二种形式用于所有其他情况。

Fortran 文件#

此模板转换器将根据“<…>”中的规则,复制文件中所有包含“<…>”的名称的**函数**和**子程序**块。“<…>”中逗号分隔的单词数决定了块重复的次数。这些单词指示“<…>”重复规则在每个块中应该替换为什么。一个块中所有重复规则都必须包含相同数量的逗号分隔的单词,指示该块应重复的次数。如果重复规则中的单词需要逗号、左箭头或右箭头,则在其前面加上反斜杠“\”。如果重复规则中的单词与“\<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文件生成的config.h文件的路径,该文件由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时,返回相对于parent_path的模块路径。也处理__main____builtin__模块。

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

待办事项:此功能可以轻松扩展到Fortran 90代码。如果您需要此功能,请告知我们。