numpy.distutils 用户指南#
警告
numpy.distutils 已弃用,并将被移除 for 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 包,可以通过 scipy 命名空间供 Python 用户使用。每个 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(..) 函数期望的关键字相同,例如 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的基名。用于子包名称的任何*都会被展开为通配符。config.add_subpackage(subpackage_name,subpackage_path=None)— 将 SciPy 子包配置添加到当前配置中。参数的含义和用法如上所述,请参阅config.get_subpackage()方法。config.add_data_files(*files)— 将files追加到data_files列表。如果files项是元组,则其第一个元素定义了相对于包安装目录复制数据文件的后缀,第二个元素指定了数据文件的路径。默认情况下,数据文件会被复制到包安装目录下的data_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_files列表中。如果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_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.distutils的build_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()— 返回 distutilsDistribution实例。config.get_config_cmd()— 返回numpy.distutilsconfig 命令实例。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 文件#
此模板转换器将文件中名称包含“<…>”的所有 **function** 和 **subroutine** 块按照“<…>”中的规则进行复制。 “<…>”中逗号分隔的单词数量决定了该块被重复的次数。这些单词是什么表明了该重复规则“<…>”将在每个块中被替换成什么。块中的所有重复规则必须包含相同数量的逗号分隔的单词,表示该块应该被重复的次数。如果重复规则中的单词需要逗号、左箭头或右箭头,则在其前面加上反斜杠“ \”。如果重复规则中的单词匹配“ \<index>”,则它将被替换为同一重复规范中的 <index>-th 单词。重复规则有两种形式:命名式和简式。
命名式重复规则#
当同一组重复项必须在块中使用多次时,命名式重复规则很有用。它使用 <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**/。
/**begin repeat独占一行,表示应该重复的片段的开始。命名式变量扩展使用
#name=item1, item2, item3, ..., itemN#定义,并放置在连续的行上。在每个重复块中,这些变量将被相应的单词替换。同一个重复块中的所有命名式变量必须定义相同数量的单词。在指定命名式变量的重复规则时,
item*N是item, item, ..., item重复 N 次的简写。此外,括号与*N结合使用可以对应该重复的多个项进行分组。因此,#name=(item1, item2)*4#等同于#name=item1, item2, item1, item2, item1, item2, item1, item2#。*/独占一行,表示前一行是将被重复块的最后一行。下一行是将被重复块使用命名式规则处理的第一行。在要重复的块内部,应该展开的变量指定为
@name@。/**end repeat**/独占一行,表示前一行是将被重复块的最后一行。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 基本包含目录的列表。NumPy 基本包含目录包含头文件,如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_path的path。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_sourcesget_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)— 将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 编译器会在源文件的前 20 行中查找以下模式
CF77FLAGS(<fcompiler type>) = <fcompiler f77flags>
并在指定类型的 fcompiler(第一个字符 C 是可选的)中使用 f77flags。
TODO:此功能可以轻松扩展到 Fortran 90 代码。如果您需要此功能,请告知我们。