NumPy 源码解析(二)
.\numpy\benchmarks\benchmarks\bench_ufunc_strides.py
# 从 common 模块导入 Benchmark 类和 get_data 函数
from .common import Benchmark, get_data
# 导入 numpy 库并将其命名为 np
import numpy as np
# 从 numpy 的内部 umath 模块中筛选出所有的 ufunc 对象并存储在 UFUNCS 列表中
UFUNCS = [obj for obj in np._core.umath.__dict__.values() if
isinstance(obj, np.ufunc)]
# 筛选出所有具有 "O->O" 类型签名的一元 ufunc 对象并存储在 UFUNCS_UNARY 列表中
UFUNCS_UNARY = [uf for uf in UFUNCS if "O->O" in uf.types]
# 创建一个 Benchmark 的子类 _AbstractBinary
class _AbstractBinary(Benchmark):
# 参数列表为空
params = []
# 参数名称列表,包括 'ufunc', 'stride_in0', 'stride_in1', 'stride_out', 'dtype'
param_names = ['ufunc', 'stride_in0', 'stride_in1', 'stride_out', 'dtype']
# 设置超时时间为 10 秒
timeout = 10
# 数组长度为 10000
arrlen = 10000
# 数据是有限的
data_finite = True
# 数据中没有非规范数
data_denormal = False
# 数据中没有零值
data_zeros = False
# 设置初始化方法,接受 ufunc、stride_in0、stride_in1、stride_out 和 dtype 参数
def setup(self, ufunc, stride_in0, stride_in1, stride_out, dtype):
# 构建 ufunc 的输入签名
ufunc_insig = f'{dtype}{dtype}->'
# 如果 ufunc 的输入签名不在 ufunc 的类型列表中
if ufunc_insig + dtype not in ufunc.types:
# 在可能的签名中查找匹配的签名
for st_sig in (ufunc_insig, dtype):
test = [sig for sig in ufunc.types if sig.startswith(st_sig)]
if test:
break
# 如果没有找到匹配的签名,则抛出 NotImplementedError 异常
if not test:
raise NotImplementedError(
f"Ufunc {ufunc} doesn't support "
f"binary input of dtype {dtype}"
) from None
# 将找到的输入和输出类型分割出来
tin, tout = test[0].split('->')
else:
# 否则,直接使用指定的输入和输出类型
tin = dtype + dtype
tout = dtype
# 初始化 ufunc_args 列表
self.ufunc_args = []
# 对于每个输入类型和对应的步长,生成测试数据并添加到 ufunc_args 中
for i, (dt, stride) in enumerate(zip(tin, (stride_in0, stride_in1))):
self.ufunc_args += [get_data(
self.arrlen * stride, dt, i,
zeros=self.data_zeros,
finite=self.data_finite,
denormal=self.data_denormal,
)[::stride]]
# 对于每个输出类型,生成空数组并添加到 ufunc_args 中
for dt in tout:
self.ufunc_args += [
np.empty(stride_out * self.arrlen, dt)[::stride_out]
]
# 设置忽略所有的 numpy 错误
np.seterr(all='ignore')
# 定义执行二元操作的方法 time_binary
def time_binary(self, ufunc, stride_in0, stride_in1, stride_out,
dtype):
ufunc(*self.ufunc_args)
# 定义执行带有第一个标量输入的二元操作的方法 time_binary_scalar_in0
def time_binary_scalar_in0(self, ufunc, stride_in0, stride_in1,
stride_out, dtype):
ufunc(self.ufunc_args[0][0], *self.ufunc_args[1:])
# 定义执行带有第二个标量输入的二元操作的方法 time_binary_scalar_in1
def time_binary_scalar_in1(self, ufunc, stride_in0, stride_in1,
stride_out, dtype):
ufunc(self.ufunc_args[0], self.ufunc_args[1][0], *self.ufunc_args[2:])
# 创建一个 Benchmark 的子类 _AbstractUnary
class _AbstractUnary(Benchmark):
# 参数列表为空
params = []
# 参数名称列表,包括 'ufunc', 'stride_in', 'stride_out', 'dtype'
param_names = ['ufunc', 'stride_in', 'stride_out', 'dtype']
# 设置超时时间为 10 秒
timeout = 10
# 数组长度为 10000
arrlen = 10000
# 数据是有限的
data_finite = True
# 数据中没有非规范数
data_denormal = False
# 数据中没有零值
data_zeros = False
# 设置对象的初始状态,准备用于运算的输入数据和参数
def setup(self, ufunc, stride_in, stride_out, dtype):
# 调用函数获取特定条件下的输入数据数组
arr_in = get_data(
stride_in*self.arrlen, dtype,
zeros=self.data_zeros,
finite=self.data_finite,
denormal=self.data_denormal,
)
# 将输入数据的切片作为ufunc的参数列表中的第一个参数
self.ufunc_args = [arr_in[::stride_in]]
# 构建ufunc的输入签名
ufunc_insig = f'{dtype}->'
# 检查ufunc是否支持指定的输入数据类型
if ufunc_insig+dtype not in ufunc.types:
# 如果不支持,尝试查找兼容的输入类型
test = [sig for sig in ufunc.types if sig.startswith(ufunc_insig)]
if not test:
# 如果找不到兼容的输入类型,抛出未实现错误
raise NotImplementedError(
f"Ufunc {ufunc} doesn't support "
f"unary input of dtype {dtype}"
) from None
# 获取找到的第一个兼容类型的输出类型
tout = test[0].split('->')[1]
else:
# 如果支持指定的输入类型,则直接使用该类型作为输出类型
tout = dtype
# 为每个输出类型创建空数组,并作为ufunc的参数列表中的一部分
for dt in tout:
self.ufunc_args += [
np.empty(stride_out*self.arrlen, dt)[::stride_out]
]
# 设置忽略所有数值错误的运算环境
np.seterr(all='ignore')
# 执行ufunc的一元运算,使用预设的参数
def time_unary(self, ufunc, stride_in, stride_out, dtype):
ufunc(*self.ufunc_args)
class UnaryFP(_AbstractUnary):
# 定义类属性 params,包含一组列表,用于参数化基本的一元操作
params = [[uf for uf in UFUNCS_UNARY
if uf not in (np.invert, np.bitwise_count)],
[1, 4],
[1, 2],
['e', 'f', 'd']]
# 定义设置方法,用于配置一元操作的参数
def setup(self, ufunc, stride_in, stride_out, dtype):
# 调用父类的设置方法,进行基本的一元操作的设置
_AbstractUnary.setup(self, ufunc, stride_in, stride_out, dtype)
# 如果操作函数名称为 'arccosh',则将参数列表的第一个元素加上 1.0
if (ufunc.__name__ == 'arccosh'):
self.ufunc_args[0] += 1.0
class UnaryFPSpecial(UnaryFP):
# 设置数据属性,标识数据不是有限的、可能有非规格化数、可能有零值
data_finite = False
data_denormal = True
data_zeros = True
class BinaryFP(_AbstractBinary):
# 定义类属性 params,包含一组列表,用于参数化基本的二元浮点操作
params = [
[np.maximum, np.minimum, np.fmax, np.fmin, np.ldexp],
[1, 2], [1, 4], [1, 2, 4], ['f', 'd']
]
class BinaryFPSpecial(BinaryFP):
# 设置数据属性,标识数据不是有限的、可能有非规格化数、可能有零值
data_finite = False
data_denormal = True
data_zeros = True
class BinaryComplex(_AbstractBinary):
# 定义类属性 params,包含一组列表,用于参数化基本的二元复数操作
params = [
[np.add, np.subtract, np.multiply, np.divide],
[1, 2, 4], [1, 2, 4], [1, 2, 4],
['F', 'D']
]
class UnaryComplex(_AbstractUnary):
# 定义类属性 params,包含一组列表,用于参数化基本的一元复数操作
params = [
[np.reciprocal, np.absolute, np.square, np.conjugate],
[1, 2, 4], [1, 2, 4], ['F', 'D']
]
class BinaryInt(_AbstractBinary):
# 设置数组长度为 100000
arrlen = 100000
# 定义类属性 params,包含一组列表,用于参数化基本的二元整数操作
params = [
[np.maximum, np.minimum],
[1, 2], [1, 2], [1, 2],
['b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q']
]
class BinaryIntContig(_AbstractBinary):
# 定义类属性 params,包含一组列表,用于参数化基本的连续的二元整数操作
params = [
[getattr(np, uf) for uf in (
'add', 'subtract', 'multiply', 'bitwise_and', 'bitwise_or',
'bitwise_xor', 'logical_and', 'logical_or', 'logical_xor',
'right_shift', 'left_shift',
)],
[1], [1], [1],
['b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q']
]
class UnaryIntContig(_AbstractUnary):
# 设置数组长度为 100000
arrlen = 100000
# 定义类属性 params,包含一组列表,用于参数化基本的连续的一元整数操作
params = [
[getattr(np, uf) for uf in (
'positive', 'square', 'reciprocal', 'conjugate', 'logical_not',
'invert', 'isnan', 'isinf', 'isfinite',
'absolute', 'sign', 'bitwise_count'
)],
[1], [1],
['b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q']
]
class Mandelbrot(Benchmark):
# 定义方法 f,用于计算绝对值是否小于 4.0
def f(self,z):
return np.abs(z) < 4.0
# 定义方法 g,用于计算 z*z + c 的和
def g(self,z,c):
return np.sum(np.multiply(z, z) + c)
# 定义方法 mandelbrot_numpy,用于计算 Mandelbrot 集合的数据
def mandelbrot_numpy(self, c, maxiter):
output = np.zeros(c.shape, np.int32)
z = np.empty(c.shape, np.complex64)
for it in range(maxiter):
notdone = self.f(z)
output[notdone] = it
z[notdone] = self.g(z[notdone],c[notdone])
output[output == maxiter-1] = 0
return output
# 定义方法 mandelbrot_set,用于生成 Mandelbrot 集合的数据
def mandelbrot_set(self,xmin,xmax,ymin,ymax,width,height,maxiter):
r1 = np.linspace(xmin, xmax, width, dtype=np.float32)
r2 = np.linspace(ymin, ymax, height, dtype=np.float32)
c = r1 + r2[:,None]*1j
n3 = self.mandelbrot_numpy(c,maxiter)
return (r1,r2,n3.T)
# 定义方法 time_mandel,用于测试 Mandelbrot 集合的计算时间
def time_mandel(self):
self.mandelbrot_set(-0.74877,-0.74872,0.06505,0.06510,1000,1000,2048)
# LogisticRegression 类继承自 Benchmark 类,用于逻辑回归模型的训练和性能评估
class LogisticRegression(Benchmark):
# 参数名称列表
param_names = ['dtype']
# 参数取值列表,包括 np.float32 和 np.float64
params = [np.float32, np.float64]
# 设置超时时间为 1000 毫秒
timeout = 1000
# 训练逻辑回归模型的方法,接受最大训练周期 max_epoch 作为参数
def train(self, max_epoch):
# 迭代训练 max_epoch 次
for epoch in range(max_epoch):
# 计算 z = X_train * W
z = np.matmul(self.X_train, self.W)
# 计算 A = sigmoid(z)
A = 1 / (1 + np.exp(-z))
# 计算交叉熵损失函数
loss = -np.mean(self.Y_train * np.log(A) + (1 - self.Y_train) * np.log(1 - A))
# 计算 dz = A - Y_train
dz = A - self.Y_train
# 计算 dw = (1/size) * X_train^T * dz
dw = (1 / self.size) * np.matmul(self.X_train.T, dz)
# 更新权重 W
self.W = self.W - self.alpha * dw
# 设置方法,初始化训练数据和模型参数
def setup(self, dtype):
# 设置随机种子为 42,以便结果可重复
np.random.seed(42)
# 训练集大小为 250
self.size = 250
# 特征数为 16
features = 16
# 生成随机训练数据 X_train,并将其类型转换为指定的 dtype
self.X_train = np.random.rand(self.size, features).astype(dtype)
# 生成随机标签数据 Y_train,并将其类型转换为指定的 dtype
self.Y_train = np.random.choice(2, self.size).astype(dtype)
# 初始化权重 W,全零数组,形状为 (features, 1),类型为 dtype
self.W = np.zeros((features, 1), dtype=dtype)
# 初始化偏置 b,全零数组,形状为 (1, 1),类型为 dtype
self.b = np.zeros((1, 1), dtype=dtype)
# 学习率设为 0.1
self.alpha = 0.1
# 性能评估方法,调用 train 方法训练模型 1000 次
def time_train(self, dtype):
self.train(1000)
.\numpy\benchmarks\benchmarks\common.py
# 导入必要的库
import numpy as np # 导入NumPy库,用于科学计算
import random # 导入random库,用于生成随机数
import os # 导入os库,提供与操作系统交互的功能
from functools import lru_cache # 从functools模块导入lru_cache装饰器,用于缓存函数的结果
from pathlib import Path # 导入Path类,用于处理文件路径
# Various pre-crafted datasets/variables for testing
# !!! Must not be changed -- only appended !!!
# while testing numpy we better not rely on numpy to produce random
# sequences
random.seed(1) # 设置随机数种子为1,确保随机数序列的可重复性
np.random.seed(1) # 设置NumPy随机数种子为1,确保NumPy生成的随机数序列的可重复性
nx, ny = 1000, 1000 # 定义变量nx和ny,表示数据矩阵的维度为1000x1000
# reduced squares based on indexes_rand, primarily for testing more
# time-consuming functions (ufunc, linalg, etc)
nxs, nys = 100, 100 # 定义变量nxs和nys,表示较小的数据矩阵的维度为100x100
# a list of interesting types to test
TYPES1 = [
'int16', 'float16',
'int32', 'float32',
'int64', 'float64', 'complex64',
'complex128',
] # 定义列表TYPES1,包含不同的数据类型,用于测试
DLPACK_TYPES = [
'int16', 'float16',
'int32', 'float32',
'int64', 'float64', 'complex64',
'complex128', 'bool',
] # 定义列表DLPACK_TYPES,包含不同的数据类型和bool类型,用于测试
# Path for caching
CACHE_ROOT = Path(__file__).resolve().parent.parent / 'env' / 'numpy_benchdata'
# 定义路径变量CACHE_ROOT,指定缓存数据的根目录路径
# values which will be used to construct our sample data matrices
# replicate 10 times to speed up initial imports of this helper
# and generate some redundancy
@lru_cache(typed=True)
def get_values():
"""
Generate and cache random values for constructing sample data matrices.
Returns
-------
values: ndarray
Random values for constructing matrices.
"""
rnd = np.random.RandomState(1804169117) # 创建一个NumPy随机状态对象
values = np.tile(rnd.uniform(0, 100, size=nx*ny//10), 10) # 生成重复的随机数值
return values
@lru_cache(typed=True)
def get_square(dtype):
"""
Generate and cache a square matrix of given data type.
Parameters
----------
dtype: str
Data type for the matrix elements.
Returns
-------
arr: ndarray
Generated square matrix.
"""
values = get_values() # 调用get_values函数获取随机数值
arr = values.astype(dtype=dtype).reshape((nx, ny)) # 根据给定数据类型生成矩阵
if arr.dtype.kind == 'c':
arr += arr.T*1j # 如果数据类型是复数,调整使得虚部非退化
return arr
@lru_cache(typed=True)
def get_squares():
"""
Generate and cache square matrices for all types in TYPES1.
Returns
-------
squares: dict
Dictionary mapping data types to their respective square matrices.
"""
return {t: get_square(t) for t in sorted(TYPES1)} # 生成不同数据类型的方阵,并以字典形式返回
@lru_cache(typed=True)
def get_square_(dtype):
"""
Generate and cache a smaller square matrix of given data type.
Parameters
----------
dtype: str
Data type for the matrix elements.
Returns
-------
arr: ndarray
Generated smaller square matrix.
"""
arr = get_square(dtype) # 调用get_square函数获取指定数据类型的方阵
return arr[:nxs, :nys] # 返回指定大小的子矩阵
@lru_cache(typed=True)
def get_squares_():
"""
Generate and cache smaller square matrices for all types in TYPES1.
Returns
-------
squares: dict
Dictionary mapping data types to their respective smaller square matrices.
"""
return {t: get_square_(t) for t in sorted(TYPES1)} # 生成不同数据类型的小方阵,并以字典形式返回
@lru_cache(typed=True)
def get_indexes():
"""
Generate and cache a list of indexes for data manipulation.
Returns
-------
indexes: ndarray
Array of indexes.
"""
indexes = list(range(nx)) # 生成包含所有索引的列表
indexes.pop(5) # 移除索引为5的元素
indexes.pop(95) # 移除索引为95的元素
indexes = np.array(indexes) # 转换为NumPy数组
return indexes
@lru_cache(typed=True)
def get_indexes_rand():
"""
Generate and cache random indexes for data manipulation.
Returns
-------
indexes_rand: ndarray
Array of random indexes.
"""
rnd = random.Random(1) # 创建一个随机数生成器对象
indexes_rand = get_indexes().tolist() # 获取索引列表的副本
rnd.shuffle(indexes_rand) # 随机打乱索引列表
indexes_rand = np.array(indexes_rand) # 转换为NumPy数组
return indexes_rand
@lru_cache(typed=True)
def get_indexes_():
"""
Generate and cache a list of smaller indexes for data manipulation.
Returns
-------
indexes_: ndarray
Array of smaller indexes.
"""
indexes = get_indexes() # 调用get_indexes函数获取索引数组
indexes_ = indexes[indexes < nxs] # 筛选出小于nxs的索引
return indexes_
@lru_cache(typed=True)
def get_indexes_rand_():
"""
Generate and cache random smaller indexes for data manipulation.
Returns
-------
indexes_rand_: ndarray
Array of random smaller indexes.
"""
indexes_rand = get_indexes_rand() # 调用get_indexes_rand函数获取随机索引数组
indexes_rand_ = indexes_rand[indexes_rand < nxs] # 筛选出小于nxs的随机索引
return indexes_rand_
@lru_cache(typed=True)
def get_data(size, dtype, ip_num=0, zeros=False, finite=True, denormal=False):
"""
Generate a cached random array that covers several scenarios affecting benchmarks.
Parameters
----------
size: int
Array length.
dtype: dtype or dtype specifier
Data type for the array elements.
ip_num: int, optional
Placeholder for future use.
zeros: bool, optional
Whether to include zeros in the array.
finite: bool, optional
Whether to include finite numbers in the array.
denormal: bool, optional
Whether to include denormal numbers in the array.
Returns
-------
data: ndarray
Generated random array based on specified parameters.
"""
# Function details omitted for brevity
# 定义输入的整数,以避免内存超载,并为每个操作数提供唯一的数据
ip_num: int
Input number, to avoid memory overload
and to provide unique data for each operand.
# 是否在生成的数据中添加零
zeros: bool
Spreading zeros along with generated data.
# 避免在生成的浮点数中出现特殊情况,如NaN和inf
finite: bool
Avoid spreading fp special cases nan/inf.
# 是否在生成的数据中添加次标准数(denormal)
denormal:
Spreading subnormal numbers along with generated data.
"""
# 将输入的dtype转换为numpy的dtype对象
dtype = np.dtype(dtype)
# 获取dtype的名称
dname = dtype.name
# 构建缓存文件的名称,包括dtype名称、size、ip_num、zeros的标志位
cache_name = f'{dname}_{size}_{ip_num}_{int(zeros)}'
# 如果dtype是复数或者复数类型,则追加finite和denormal的标志位
if dtype.kind in 'fc':
cache_name += f'{int(finite)}{int(denormal)}'
# 将'.bin'作为文件扩展名追加到缓存文件名
cache_name += '.bin'
# 构建缓存文件的完整路径
cache_path = CACHE_ROOT / cache_name
# 如果缓存文件已存在,则从文件中读取数据并返回numpy数组
if cache_path.exists():
return np.fromfile(cache_path, dtype)
# 如果缓存文件不存在,则生成新的数据数组
array = np.ones(size, dtype)
# 生成随机数列表
rands = []
# 根据dtype的种类生成不同范围的随机整数
if dtype.kind == 'i': # 如果是有符号整数
dinfo = np.iinfo(dtype)
scale = 8
if zeros:
scale += 1
lsize = size // scale
for low, high in (
(-0x80, -1),
(1, 0x7f),
(-0x8000, -1),
(1, 0x7fff),
(-0x80000000, -1),
(1, 0x7fffffff),
(-0x8000000000000000, -1),
(1, 0x7fffffffffffffff),
):
rands += [np.random.randint(
max(low, dinfo.min),
min(high, dinfo.max),
lsize, dtype
)]
elif dtype.kind == 'u': # 如果是无符号整数
dinfo = np.iinfo(dtype)
scale = 4
if zeros:
scale += 1
lsize = size // scale
for high in (0xff, 0xffff, 0xffffffff, 0xffffffffffffffff):
rands += [np.random.randint(1, min(high, dinfo.max), lsize, dtype)]
elif dtype.kind in 'fc': # 如果是浮点数或复数
scale = 1
if zeros:
scale += 1
if not finite:
scale += 2
if denormal:
scale += 1
dinfo = np.finfo(dtype)
lsize = size // scale
rands = [np.random.rand(lsize).astype(dtype)]
if not finite:
# 如果不限制有限范围,则生成NaN和inf
rands += [
np.empty(lsize, dtype=dtype), np.empty(lsize, dtype=dtype)
]
rands[1].fill(float('nan'))
rands[2].fill(float('inf'))
if denormal:
# 如果允许生成次标准数,则填充最小次标准数
rands += [np.empty(lsize, dtype=dtype)]
rands[-1].fill(dinfo.smallest_subnormal)
# 如果rands非空,则将生成的随机数填充到array中
if rands:
if zeros:
# 如果需要在数组中填充零,则生成一个零数组,并按步长填充到array中
rands += [np.zeros(lsize, dtype)]
stride = len(rands)
for start, r in enumerate(rands):
array[start:len(r)*stride:stride] = r
# 如果缓存根目录不存在,则创建之
if not CACHE_ROOT.exists():
CACHE_ROOT.mkdir(parents=True)
# 将生成的数据数组写入缓存文件
array.tofile(cache_path)
# 返回生成的数据数组
return array
# 定义一个名为 Benchmark 的空类,用于作为基准类或者后续扩展的基础
class Benchmark:
pass
.\numpy\benchmarks\benchmarks\__init__.py
# 导入名为 common 的当前目录下的模块
from . import common
# 导入 sys 和 os 模块
import sys
import os
# 定义函数 show_cpu_features
def show_cpu_features():
# 从 numpy 库中导入 _opt_info 函数
from numpy.lib._utils_impl import _opt_info
# 调用 _opt_info 函数获取信息
info = _opt_info()
# 构造 info 字符串,描述 NumPy 的 CPU 特性信息
info = "NumPy CPU features: " + (info if info else 'nothing enabled')
# 检查是否在环境变量中找到 'SHELL',并且不在 Windows 平台上
if 'SHELL' in os.environ and sys.platform != 'win32':
# 在终端中输出带有黄色前景色的 info 字符串
print(f"\033[33m{info}\033[0m")
else:
# 在终端中输出 info 字符串
print(info)
# 定义函数 dirty_lock
def dirty_lock(lock_name, lock_on_count=1):
# 检查当前操作系统是否具有 getppid 函数
if not hasattr(os, "getppid"):
return False
# 获取当前进程的父进程 ID
ppid = os.getppid()
# 如果没有父进程或者父进程 ID 等于当前进程 ID,则返回 False
if not ppid or ppid == os.getpid():
return False
# 构造锁文件的路径
lock_path = os.path.abspath(os.path.join(
os.path.dirname(__file__), "..", "env", lock_name)
)
# 尝试在锁文件中进行加锁操作
try:
with open(lock_path, 'a+') as f:
f.seek(0)
# 读取锁文件中的计数和父进程 ID
count, _ppid = (f.read().split() + [0, 0])[:2]
count, _ppid = int(count), int(_ppid)
# 如果父进程 ID 相同,则进行锁计数逻辑
if _ppid == ppid:
if count >= lock_on_count:
return True
count += 1
else:
count = 0
# 清空并更新锁文件内容为新的计数和父进程 ID
f.seek(0)
f.truncate()
f.write(f"{str(count)} {str(ppid)}")
except OSError:
pass
return False
# 如果未能获取到名为 "print_cpu_features.lock" 的脏锁,则调用 show_cpu_features 函数
if not dirty_lock("print_cpu_features.lock"):
show_cpu_features()
NumPy Logo Guidelines
These guidelines are meant to help keep the NumPy logo consistent and recognizable across all its uses. They also provide a common language for referring to the logos and their components.
The primary logo is the horizontal option (logomark and text next to each other) and the secondary logo is the stacked version (logomark over text). I’ve also provided the logomark on its own (meaning it doesn’t have text). When in doubt, it’s preferable to use primary or secondary options over the logomark alone.
Color
The full color options are a combo of two shades of blue, rgb(77, 171, 207) and rgb(77, 119, 207), while light options are rgb(255, 255, 255) and dark options are rgb(1, 50, 67).
Whenever possible, use the full color logos. One color logos (light or dark) are to be used when full color will not have enough contrast, usually when logos must be on colored backgrounds.
Minimum Size
Please do not make the primary logo smaller than 50px wide, secondary logo smaller than 35px wide, or logomark smaller than 20px wide.
Logo Integrity
A few other notes to keep in mind when using the logo:
- Make sure to scale the logo proportionally.
- Maintain a good amount of space around the logo. Don’t let it overlap with text, images, or other elements.
- Do not try and recreate or modify the logo. For example, do not use the logomark and then try to write NumPy in another font.
Building with Meson
Note: this is for early adopters. It has been tested on Linux and macOS, and with Python 3.10-3.12. There is one CI job to keep the build stable. This may have rough edges, please open an issue if you run into a problem.
Developer build
Install build tools: Use one of:
-
mamba env create -f environment.yml && mamba activate numpy-dev -
python -m pip install -r requirements/build_requirements.txtNote: also make sure you havepkg-configand the usual system dependencies for NumPy
Then install spin:
python -m pip install spin
Compile and install: spin build
This builds in the build/ directory, and installs into the build-install directory.
Then run the test suite or a shell via spin:
spin test
spin ipython
Alternatively, to use the package, add it to your PYTHONPATH:
export PYTHONPATH=${PWD}/build/lib64/python3.10/site-packages # may vary
pytest --pyargs numpy
pip install
Note that pip will use the default build system, which is now Meson.
Commands such as pip install . or pip install --no-build-isolation .
will work as expected, as does building an sdist or wheel with python -m build,
or pip install -e . --no-build-isolation for an editable install.
For a more complete developer experience than editable installs, consider using
spin instead though (see above).
Workaround for a hiccup on Fedora
- Fedora does not distribute
openblas.pc. Install the following file in~/lib/pkgconfig/openblas.pc:
prefix=/usr
includedir=${prefix}/include
libdir=${prefix}/lib64
Name: openblas
Description: OpenBLAS is an optimized BLAS library based on GotoBLAS2 1.13 BSD version
Version: 0.3.19
Cflags: -I${includedir}/openblas
Libs: -L${libdir} -lopenblas
Then build with:
spin build -- -Dpkg_config_path=${HOME}/lib/pkgconfig
.\numpy\doc\conftest.py
"""
Pytest configuration and fixtures for the Numpy test suite.
"""
# 导入 pytest 库,用于测试框架
import pytest
# 导入 numpy 库,用于科学计算
import numpy
# 导入 matplotlib 库,用于绘图
import matplotlib
# 导入 doctest 库,用于文档测试
import doctest
# 设置 matplotlib 使用后端 'agg',强制使用该后端
matplotlib.use('agg', force=True)
# 忽略 matplotlib 输出,如 `<matplotlib.image.AxesImage at
# 0x7f956908c280>`。使用 doctest 的 monkeypatching 实现,
# 受 https://github.com/wooyek/pytest-doctest-ellipsis-markers (MIT license) 启发
# 定义一个自定义的输出检查器类,继承自 doctest.OutputChecker
OutputChecker = doctest.OutputChecker
# 定义要忽略的空行标记列表,如 '<matplotlib.', '<mpl_toolkits.mplot3d.'
empty_line_markers = ['<matplotlib.', '<mpl_toolkits.mplot3d.']
class SkipMatplotlibOutputChecker(doctest.OutputChecker):
def check_output(self, want, got, optionflags):
# 遍历空行标记列表,如果输出中包含其中之一的标记,则将 got 设为空字符串
for marker in empty_line_markers:
if marker in got:
got = ''
break
# 调用父类的 check_output 方法检查输出
return OutputChecker.check_output(self, want, got, optionflags)
# 将 doctest.OutputChecker 替换为自定义的 SkipMatplotlibOutputChecker
doctest.OutputChecker = SkipMatplotlibOutputChecker
# 定义一个自动使用的 fixture,向 doctest 的命名空间中添加 'np',值为 numpy
@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
# 设置 numpy 的随机种子为 1
numpy.random.seed(1)
# 向 doctest 的命名空间中添加 'np',值为 numpy
doctest_namespace['np'] = numpy
.\numpy\doc\neps\conf.py
#
# NumPy Enhancement Proposals documentation build configuration file, created by
# sphinx-quickstart on Mon Dec 11 12:45:09 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.imgmath', # 导入数学公式图片生成扩展模块
'sphinx.ext.intersphinx', # 导入交叉引用扩展模块
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['../source/_templates/']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'content'
# General information about the project.
project = 'NumPy Enhancement Proposals' # 项目名称为“NumPy Enhancement Proposals”
copyright = '2017-2018, NumPy Developers'
author = 'NumPy Developers'
title = 'NumPy Enhancement Proposals Documentation' # 文档标题为“NumPy Enhancement Proposals Documentation”
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = ''
# The full version, including alpha/beta/rc tags.
release = ''
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en" # 使用英语作为生成文档的语言
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
## -- Options for HTML output ----------------------------------------------
#
html_theme = 'pydata_sphinx_theme' # 使用PyData Sphinx主题
html_logo = '../source/_static/numpylogo.svg' # 设置HTML页面的Logo图标
html_favicon = '../source/_static/favicon/favicon.ico' # 设置HTML页面的favicon图标
# 定义 HTML 主题的选项字典
html_theme_options = {
# GitHub 项目的 URL
"github_url": "https://github.com/numpy/numpy",
# 外部链接列表,包含一个字典,指定链接名称和 URL
"external_links": [
{"name": "Wishlist",
"url": "https://github.com/numpy/numpy/issues?q=is%3Aopen+is%3Aissue+label%3A%2223+-+Wish+List%22",
},
],
# 是否显示上一篇和下一篇链接,默认为 False
"show_prev_next": False,
}
# 设置 HTML 页面的标题,使用项目名称进行格式化
html_title = "%s" % (project)
# 设置静态文件路径,指向源文件中的 _static 文件夹
html_static_path = ['../source/_static']
# 设置 HTML 页面中最后更新时间的格式
html_last_updated_fmt = '%b %d, %Y'
# 设置是否使用模块索引,默认为 True
html_use_modindex = True
# 设置是否复制源文件到输出目录,默认为 False
html_copy_source = False
# 设置是否显示域索引,默认为 False
html_domain_indices = False
# 设置 HTML 页面文件的后缀名,默认为 '.html'
html_file_suffix = '.html'
# 如果 sphinx.ext.pngmath 在 extensions 列表中
if 'sphinx.ext.pngmath' in extensions:
# 设置 PNG 数学公式是否显示预览,默认为 True
pngmath_use_preview = True
# 设置 dvipng 参数,包括 gamma 值、分辨率、背景透明度等
pngmath_dvipng_args = ['-gamma', '1.5', '-D', '96', '-bg', 'Transparent']
# 设置是否显示绘图的 HTML 格式选项,默认为 False
plot_html_show_formats = False
# 设置是否显示绘图的源文件链接,默认为 False
plot_html_show_source_link = False
# -- Options for HTMLHelp output ------------------------------------------
# 设置 HTMLHelp 输出文件的基本名称
htmlhelp_basename = 'NumPyEnhancementProposalsdoc'
# -- Options for LaTeX output ---------------------------------------------
# LaTeX 文档的相关配置项
latex_elements = {
# 纸张大小,可选 'letterpaper' 或 'a4paper'
# 'papersize': 'letterpaper',
# 字体大小,可选 '10pt', '11pt' 或 '12pt'
# 'pointsize': '10pt',
# LaTeX 导言区的附加内容
# 'preamble': '',
# 图片浮动位置设定
# 'figure_align': 'htbp',
}
# LaTeX 文档列表,包括源文件、目标文件名、标题、作者、文档类别等信息
latex_documents = [
(master_doc, 'NumPyEnhancementProposals.tex', title,
'NumPy Developers', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# 每个手册页的配置项,包括源文件、名称、描述、作者、手册页章节等信息
man_pages = [
(master_doc, 'numpyenhancementproposals', title,
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Texinfo 输出文件的配置项,包括源文件、目标名称、标题、作者、目录项、描述、分类等信息
texinfo_documents = [
(master_doc, 'NumPyEnhancementProposals', title,
author, 'NumPyEnhancementProposals', 'One line description of project.',
'Miscellaneous'),
]
# -----------------------------------------------------------------------------
# Intersphinx 配置
# -----------------------------------------------------------------------------
# 将文档树分组到 Intersphinx 文件中,指定每个库的名称和对应 URL
intersphinx_mapping = {
'python': ('https://docs.python.org/dev', None),
'numpy': ('https://numpy.org/devdocs', None),
'scipy': ('https://docs.scipy.org/doc/scipy/reference', None),
'matplotlib': ('https://matplotlib.org', None)
}
.\numpy\doc\neps\nep-0016-benchmark.py
# 导入性能测试模块 perf
import perf
# 导入抽象基类模块 abc
import abc
# 导入 NumPy 库并使用 np 别名
import numpy as np
# 定义一个名为 NotArray 的空类
class NotArray:
pass
# 定义一个名为 AttrArray 的类,并设置类属性 __array_implementer__ 为 True
class AttrArray:
__array_implementer__ = True
# 定义一个名为 ArrayBase 的抽象基类
class ArrayBase(abc.ABC):
pass
# 定义一个名为 ABCArray1 的类,继承自 ArrayBase 抽象基类
class ABCArray1(ArrayBase):
pass
# 定义一个名为 ABCArray2 的类,没有明确指定继承关系
class ABCArray2:
pass
# 将 ABCArray2 注册为 ArrayBase 的虚拟子类
ArrayBase.register(ABCArray2)
# 创建 NotArray 类的实例
not_array = NotArray()
# 创建 AttrArray 类的实例
attr_array = AttrArray()
# 创建 ABCArray1 类的实例
abc_array_1 = ABCArray1()
# 创建 ABCArray2 类的实例
abc_array_2 = ABCArray2()
# 确保抽象基类 ABC 的缓存被预先加载
# 测试 isinstance 函数,检查 not_array 是否为 ArrayBase 的实例
isinstance(not_array, ArrayBase)
# 测试 isinstance 函数,检查 abc_array_1 是否为 ArrayBase 的实例
isinstance(abc_array_1, ArrayBase)
# 测试 isinstance 函数,检查 abc_array_2 是否为 ArrayBase 的实例
# 创建性能测试的 Runner 对象
runner = perf.Runner()
# 定义函数 t,用于执行性能测试并记录时间
def t(name, statement):
runner.timeit(name, statement, globals=globals())
# 测试 np.asarray([]) 的性能
t("np.asarray([])", "np.asarray([])")
# 创建一个空 NumPy 数组 arrobj
arrobj = np.array([])
# 测试 np.asarray(arrobj) 的性能
t("np.asarray(arrobj)", "np.asarray(arrobj)")
# 测试 getattr 函数,获取 not_array 的 '__array_implementer__' 属性,如果不存在返回 False
t("attr, False", "getattr(not_array, '__array_implementer__', False)")
# 测试 getattr 函数,获取 attr_array 的 '__array_implementer__' 属性,如果不存在返回 False
t("attr, True", "getattr(attr_array, '__array_implementer__', False)")
# 测试 isinstance 函数,检查 not_array 是否为 ArrayBase 的实例,预期结果为 False
t("ABC, False", "isinstance(not_array, ArrayBase)")
# 测试 isinstance 函数,检查 abc_array_1 是否为 ArrayBase 的实例,预期结果为 True(通过继承实现)
t("ABC, True, via inheritance", "isinstance(abc_array_1, ArrayBase)")
# 测试 isinstance 函数,检查 abc_array_2 是否为 ArrayBase 的实例,预期结果为 True(通过注册实现)
t("ABC, True, via register", "isinstance(abc_array_2, ArrayBase)")
.\numpy\doc\neps\tools\build_index.py
"""
Scan the directory of nep files and extract their metadata. The
metadata is passed to Jinja for filling out the toctrees for various NEP
categories.
"""
# 导入必要的模块
import os # 提供与操作系统相关的功能
import jinja2 # 模板引擎
import glob # 文件名匹配
import re # 正则表达式操作
# 渲染函数,用于渲染 Jinja 模板
def render(tpl_path, context):
# 拆分模板路径和文件名
path, filename = os.path.split(tpl_path)
# 返回渲染后的模板内容
return jinja2.Environment(
loader=jinja2.FileSystemLoader(path or './')
).get_template(filename).render(context)
# 提取 NEP 元数据的函数
def nep_metadata():
# 忽略的文件名
ignore = ('nep-template.rst')
# 匹配所有符合 nep-*.rst 模式的文件,并按文件名排序
sources = sorted(glob.glob(r'nep-*.rst'))
# 过滤掉忽略的文件名
sources = [s for s in sources if not s in ignore]
# 元数据的正则表达式模式
meta_re = r':([a-zA-Z\-]*): (.*)'
# 是否存在 Provisional 类型的 NEP
has_provisional = False
# 存储 NEP 元数据的字典
neps = {}
# 打印加载的元数据信息
print('Loading metadata for:')
# 遍历每个 NEP 文件
for source in sources:
# 打印加载的文件名
print(f' - {source}')
# 提取 NEP 编号
nr = int(re.match(r'nep-([0-9]{4}).*\.rst', source).group(1))
# 打开 NEP 文件并读取每一行内容
with open(source) as f:
lines = f.readlines()
# 提取每行中的标签信息
tags = [re.match(meta_re, line) for line in lines]
tags = [match.groups() for match in tags if match is not None]
# 将标签信息转化为字典形式
tags = {tag[0]: tag[1] for tag in tags}
# 查找 NEP 标题所在行
for i, line in enumerate(lines[:-1]):
chars = set(line.rstrip())
# 判断标题行的特征
if len(chars) == 1 and ("=" in chars or "*" in chars):
break
else:
# 如果找不到 NEP 标题行,则引发运行时错误
raise RuntimeError("Unable to find NEP title.")
# 将 NEP 标题和文件名添加到标签信息中
tags['Title'] = lines[i+1].strip()
tags['Filename'] = source
# 检查 NEP 标题是否以正确格式开始
if not tags['Title'].startswith(f'NEP {nr} — '):
raise RuntimeError(
f'Title for NEP {nr} does not start with "NEP {nr} — " '
'(note that — here is a special, elongated dash). Got: '
f' {tags["Title"]!r}')
# 检查已接受、已拒绝或已撤回的 NEP 是否有解决方案标签
if tags['Status'] in ('Accepted', 'Rejected', 'Withdrawn'):
if not 'Resolution' in tags:
raise RuntimeError(
f'NEP {nr} is Accepted/Rejected/Withdrawn but '
'has no Resolution tag'
)
# 如果 NEP 的状态是 Provisional,则设置标志位为 True
if tags['Status'] == 'Provisional':
has_provisional = True
# 将 NEP 数据存入 neps 字典中
neps[nr] = tags
# 现在已经获取了所有 NEP 元数据,可以执行一些全局一致性检查
# 遍历字典 neps,其中 nr 是 NEP 编号,tags 是 NEP 的标签字典
for nr, tags in neps.items():
# 检查 NEP 的状态是否为 'Superseded'(已废弃)
if tags['Status'] == 'Superseded':
# 如果 NEP 被标记为 'Superseded',则检查是否存在 'Replaced-By' 标签
if not 'Replaced-By' in tags:
# 如果缺少 'Replaced-By' 标签,则抛出运行时错误,指明 NEP 已废弃但未指定替代的 NEP 编号
raise RuntimeError(
f'NEP {nr} has been Superseded, but has no Replaced-By tag'
)
# 获取被替代的 NEP 编号,并转换为整数
replaced_by = int(re.findall(r'\d+', tags['Replaced-By'])[0])
# 获取替代 NEP 对象的标签信息
replacement_nep = neps[replaced_by]
# 检查替代 NEP 是否有 'Replaces' 标签
if not 'Replaces' in replacement_nep:
# 如果替代 NEP 缺少 'Replaces' 标签,则抛出运行时错误,指明当前 NEP 被替代但替代的 NEP 没有指定被替代的 NEP 编号
raise RuntimeError(
f'NEP {nr} is superseded by {replaced_by}, but that NEP has no Replaces tag.'
)
# 检查当前 NEP 是否在替代 NEP 的 'Replaces' 标签中
if nr not in parse_replaces_metadata(replacement_nep):
# 如果当前 NEP 不在替代 NEP 的 'Replaces' 标签中,则抛出运行时错误,指明当前 NEP 被替代但被替代 NEP 的 'Replaces' 标签指定了不正确的 NEP 编号
raise RuntimeError(
f'NEP {nr} is superseded by {replaced_by}, but that NEP has a Replaces tag of `{replacement_nep['Replaces']}`.'
)
# 如果当前 NEP 存在 'Replaces' 标签
if 'Replaces' in tags:
# 解析当前 NEP 的 'Replaces' 标签,并遍历每个被替代的 NEP 编号
replaced_neps = parse_replaces_metadata(tags)
for nr_replaced in replaced_neps:
# 获取被替代 NEP 对象的标签信息
replaced_nep_tags = neps[nr_replaced]
# 检查被替代 NEP 的状态是否为 'Superseded'
if not replaced_nep_tags['Status'] == 'Superseded':
# 如果被替代 NEP 的状态不是 'Superseded',则抛出运行时错误,指明当前 NEP 替代了一个未被设置为 'Superseded' 的 NEP
raise RuntimeError(
f'NEP {nr} replaces NEP {nr_replaced}, but that NEP has not been set to Superseded'
)
# 返回包含更新后的 NEP 数据字典和是否存在临时 NEP 的标志的字典
return {'neps': neps, 'has_provisional': has_provisional}
# 定义函数,处理替换的元数据中的 :Replaces: 字段,返回替换后的 NEP 编号列表
def parse_replaces_metadata(replacement_nep):
"""Handle :Replaces: as integer or list of integers"""
# 使用正则表达式查找替换的 NEP 编号,返回匹配到的所有数字字符串列表
replaces = re.findall(r'\d+', replacement_nep['Replaces'])
# 将匹配到的数字字符串列表转换为整数列表
replaced_neps = [int(s) for s in replaces]
# 返回替换后的 NEP 编号列表
return replaced_neps
# 调用 nep_metadata() 函数获取 NEP 元数据
meta = nep_metadata()
# 遍历给定的 NEP 类别列表
for nepcat in (
"provisional", "accepted", "deferred", "finished", "meta",
"open", "rejected",
):
# 构建输入文件名和输出文件名
infile = f"{nepcat}.rst.tmpl"
outfile = f"{nepcat}.rst"
# 打印编译信息,指示正在处理哪个模板文件到哪个输出文件
print(f'Compiling {infile} -> {outfile}')
# 调用 render 函数,生成指定模板文件的内容
genf = render(infile, meta)
# 打开输出文件,写入生成的内容
with open(outfile, 'w') as f:
f.write(genf)
.\numpy\doc\postprocess.py
#!/usr/bin/env python3
"""
Post-processes HTML and Latex files output by Sphinx.
"""
# 主程序入口
def main():
import argparse
# 创建命令行参数解析器
parser = argparse.ArgumentParser(description=__doc__)
# 添加参数:模式(html 或 tex)
parser.add_argument('mode', help='file mode', choices=('html', 'tex'))
# 添加参数:输入文件列表
parser.add_argument('file', nargs='+', help='input file(s)')
# 解析命令行参数
args = parser.parse_args()
# 获取模式参数
mode = args.mode
# 遍历每个输入文件
for fn in args.file:
# 打开文件并读取内容
with open(fn, encoding="utf-8") as f:
# 根据模式选择处理函数处理文件内容
if mode == 'html':
lines = process_html(fn, f.readlines())
elif mode == 'tex':
lines = process_tex(f.readlines())
# 将处理后的内容写回文件
with open(fn, 'w', encoding="utf-8") as f:
f.write("".join(lines))
# 处理 HTML 文件内容的函数
def process_html(fn, lines):
return lines
# 处理 LaTeX 文件内容的函数
def process_tex(lines):
"""
Remove unnecessary section titles from the LaTeX file.
移除 LaTeX 文件中不必要的章节标题。
"""
new_lines = []
# 遍历每一行内容
for line in lines:
# 如果是以特定的 numpy 相关标题开头,则跳过这一行
if (line.startswith(r'\section{numpy.')
or line.startswith(r'\subsection{numpy.')
or line.startswith(r'\subsubsection{numpy.')
or line.startswith(r'\paragraph{numpy.')
or line.startswith(r'\subparagraph{numpy.')
):
pass # 跳过这些行!
else:
# 否则将这一行添加到新的内容列表中
new_lines.append(line)
return new_lines
# 如果作为主程序运行,则调用 main 函数
if __name__ == "__main__":
main()
.\numpy\doc\preprocess.py
#!/usr/bin/env python3
# 指定解释器
import subprocess
# 引入subprocess模块,用于生成子进程执行外部命令
import os
# 引入os模块,用于提供与操作系统交互的函数
import sys
# 引入sys模块,用于提供Python解释器与Python环境的相关功能
from string import Template
# 从string模块中引入Template类,用于字符串模板化
def main():
# 调用doxy_gen函数,参数为程序的绝对路径
doxy_gen(os.path.abspath(os.path.join('..')))
def doxy_gen(root_path):
"""
Generate Doxygen configuration file.
"""
# 调用doxy_config函数,参数为根路径
confs = doxy_config(root_path)
# 构建文档生成路径
build_path = os.path.join(root_path, "doc", "build", "doxygen")
# 生成文件路径
gen_path = os.path.join(build_path, "Doxyfile")
# 如果生成文件路径不存在,则创建
if not os.path.exists(build_path):
os.makedirs(build_path)
# 以写入模式打开生成文件路径
with open(gen_path, 'w') as fd:
# 写入注释
fd.write("#Please Don't Edit! This config file was autogenerated by ")
# 写入注释
fd.write(f"doxy_gen({root_path}) in doc/preprocess.py.\n")
# 迭代写入配置信息
for c in confs:
fd.write(c)
class DoxyTpl(Template):
delimiter = '@'
def doxy_config(root_path):
"""
Fetch all Doxygen sub-config files and gather it with the main config file.
"""
# 子配置列表
confs = []
# Doxygen源文件路径
dsrc_path = os.path.join(root_path, "doc", "source")
# 子配置信息
sub = dict(ROOT_DIR=root_path)
# 以读取模式打开Doxyfile文件
with open(os.path.join(dsrc_path, "doxyfile")) as fd:
# 实例化模板对象
conf = DoxyTpl(fd.read())
# 格式化子配置
confs.append(conf.substitute(CUR_DIR=dsrc_path, **sub))
# 遍历根路径下的所有文件和目录
for dpath, _, files in os.walk(root_path):
# 如果.doxyfile文件不在文件列表中,则继续循环
if ".doxyfile" not in files:
continue
# 获取子配置文件路径
conf_path = os.path.join(dpath, ".doxyfile")
# 以读取模式打开子配置文件
with open(conf_path) as fd:
# 实例化模板对象
conf = DoxyTpl(fd.read())
# 格式化子配置
confs.append(conf.substitute(CUR_DIR=dpath, **sub))
# 返回所有配置信息
return confs
if __name__ == "__main__":
# 调用main函数
main()
.\numpy\doc\source\conf.py
# 导入必要的标准库和第三方库
import os # 操作系统接口
import re # 正则表达式
import sys # 系统特定的参数和函数
import importlib # 实现动态加载模块和包
from docutils import nodes # 文档处理工具
from docutils.parsers.rst import Directive # reStructuredText 的指令
# Minimum version, enforced by sphinx
# 最低版本要求,由 Sphinx 强制执行
needs_sphinx = '4.3'
# This is a nasty hack to use platform-agnostic names for types in the
# documentation.
# 这是一个不好的 hack,用于在文档中使用与平台无关的类型名称。
# must be kept alive to hold the patched names
# 必须保持存活状态以保存打补丁的名称
_name_cache = {}
def replace_scalar_type_names():
""" Rename numpy types to use the canonical names to make sphinx behave """
""" 重命名 numpy 类型以使用规范名称,使 sphinx 正常工作 """
import ctypes
Py_ssize_t = ctypes.c_int64 if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_int32
class PyObject(ctypes.Structure):
pass
class PyTypeObject(ctypes.Structure):
pass
PyObject._fields_ = [
('ob_refcnt', Py_ssize_t),
('ob_type', ctypes.POINTER(PyTypeObject)),
]
PyTypeObject._fields_ = [
# varhead
('ob_base', PyObject),
('ob_size', Py_ssize_t),
# declaration
('tp_name', ctypes.c_char_p),
]
# prevent numpy attaching docstrings to the scalar types
# 防止 numpy 将文档字符串附加到标量类型上
assert 'numpy._core._add_newdocs_scalars' not in sys.modules
sys.modules['numpy._core._add_newdocs_scalars'] = object()
import numpy
# change the __name__ of the scalar types
# 更改标量类型的 __name__
for name in [
'byte', 'short', 'intc', 'int_', 'longlong',
'ubyte', 'ushort', 'uintc', 'uint', 'ulonglong',
'half', 'single', 'double', 'longdouble',
'half', 'csingle', 'cdouble', 'clongdouble',
]:
typ = getattr(numpy, name)
c_typ = PyTypeObject.from_address(id(typ))
c_typ.tp_name = _name_cache[typ] = b"numpy." + name.encode('utf8')
# now generate the docstrings as usual
# 现在像往常一样生成文档字符串
del sys.modules['numpy._core._add_newdocs_scalars']
import numpy._core._add_newdocs_scalars
replace_scalar_type_names()
# As of NumPy 1.25, a deprecation of `str`/`bytes` attributes happens.
# For some reasons, the doc build accesses these, so ignore them.
# 从 NumPy 1.25 开始,将会弃用 `str`/`bytes` 属性。
# 由于某些原因,文档生成访问了这些属性,因此忽略它们。
import warnings
warnings.filterwarnings("ignore", "In the future.*NumPy scalar", FutureWarning)
# -----------------------------------------------------------------------------
# General configuration
# -----------------------------------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
# 在这里添加任何 Sphinx 扩展模块名称,作为字符串。它们可以是随 Sphinx 一起提供的扩展(如 'sphinx.ext.*')或您自己的扩展。
sys.path.insert(0, os.path.abspath('../sphinxext'))
extensions = [
'sphinx.ext.autodoc', # 自动生成 API 文档
'numpydoc', # 支持 NumPy 风格的文档
'sphinx.ext.intersphinx', # 支持链接外部文档
'sphinx.ext.coverage', # 测试覆盖率相关
'sphinx.ext.doctest', # 运行文档中的示例,并验证结果的正确性
'sphinx.ext.autosummary', # 自动生成摘要
'sphinx.ext.graphviz', # 生成图形
'sphinx.ext.ifconfig', # 条件设置配置
'matplotlib.sphinxext.plot_directive', # 绘图指令
'IPython.sphinxext.ipython_console_highlighting', # IPython 控制台高亮
'IPython.sphinxext.ipython_directive', # IPython 指令
'sphinx.ext.mathjax', # 数学公式支持
'sphinx_design', # Sphinx 主题设计扩展
]
skippable_extensions = [
('breathe', 'skip generating C/C++ API from comment blocks.'),
]
for ext, warn in skippable_extensions:
# 检查指定的 Sphinx 扩展是否存在于当前环境中
ext_exist = importlib.util.find_spec(ext) is not None
# 如果找到了指定的 Sphinx 扩展,则将其添加到列表中
if ext_exist:
extensions.append(ext)
# 如果未找到指定的 Sphinx 扩展,则打印警告信息
else:
print(f"Unable to find Sphinx extension '{ext}', {warn}.")
# Add any paths that contain templates here, relative to this directory.
# 添加包含模板的路径列表,相对于当前目录。
templates_path = ['_templates']
# The suffix of source filenames.
# 源文件名的后缀。
source_suffix = '.rst'
# General substitutions.
# 一般的替换项。
project = 'NumPy'
copyright = '2008-2024, NumPy Developers'
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
# 默认的 |version| 和 |release| 替换值,在构建文档中的多处使用。
import numpy
# The short X.Y version (including .devXXXX, rcX, b1 suffixes if present)
# 短版本 X.Y(包括 .devXXXX、rcX、b1 后缀,如果存在的话)
version = re.sub(r'(\d+\.\d+)\.\d+(.*)', r'\1\2', numpy.__version__)
version = re.sub(r'(\.dev\d+).*?$', r'\1', version)
# The full version, including alpha/beta/rc tags.
# 完整版本,包括 alpha/beta/rc 标签。
release = numpy.__version__
print("%s %s" % (version, release))
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
# 有两种方式替换 |today|:一种是将 today 设置为某个非假值,然后它会被使用;
# 另一种是使用 today_fmt 作为 strftime 调用的格式。
today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
# 不应包含在构建中的文档列表。
#unused_docs = []
# The reST default role (used for this markup: `text`) to use for all documents.
# 所有文档使用的 reST 默认角色(用于此标记:`text`)。
default_role = "autolink"
# List of directories, relative to source directories, that shouldn't be searched
# for source files.
# 不应搜索源文件的目录列表,相对于源目录。
exclude_dirs = []
exclude_patterns = []
if sys.version_info[:2] >= (3, 12):
exclude_patterns += ["reference/distutils.rst"]
# If true, '()' will be appended to :func: etc. cross-reference text.
# 如果为 True,则在 :func: 等交叉引用文本后附加 '()'。
add_function_parentheses = False
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# 如果为 True,则当前模块名将前置于所有描述单元标题之前(例如 .. function::)。
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# 如果为 True,则输出中将显示 sectionauthor 和 moduleauthor 指令。它们默认情况下被忽略。
#show_authors = False
class LegacyDirective(Directive):
"""
Adapted from docutils/parsers/rst/directives/admonitions.py
Uses a default text if the directive does not have contents. If it does,
the default text is concatenated to the contents.
See also the same implementation in SciPy's conf.py.
"""
# 来自 docutils/parsers/rst/directives/admonitions.py 的适应版本
# 如果指令没有内容,则使用默认文本。如果有内容,则将默认文本连接到内容中。
has_content = True
node_class = nodes.admonition
optional_arguments = 1
def run(self):
try:
# 尝试获取第一个参数作为对象名称
obj = self.arguments[0]
except IndexError:
# 没有参数传入时,默认使用文本 "submodule"
obj = "submodule"
# 创建包含说明文本的字符串
text = (f"This {obj} is considered legacy and will no longer receive "
"updates. This could also mean it will be removed in future "
"NumPy versions.")
try:
# 尝试将文本添加到现有内容列表的第一个位置
self.content[0] = text + " " + self.content[0]
except IndexError:
# 如果内容列表为空,则创建新的内容条目
source, lineno = self.state_machine.get_source_and_line(
self.lineno
)
self.content.append(
text,
source=source,
offset=lineno
)
# 将内容列表转换为单个文本字符串
text = '\n'.join(self.content)
# 创建警告提示节点,稍后由 `nested_parse` 填充内容
admonition_node = self.node_class(rawsource=text)
# 设置自定义标题
title_text = "Legacy"
textnodes, _ = self.state.inline_text(title_text, self.lineno)
# 创建标题节点
title = nodes.title(title_text, '', *textnodes)
admonition_node += title
# 设置警告提示节点的 CSS 类
admonition_node['classes'] = ['admonition-legacy']
# 解析指令内容并填充到警告提示节点中
self.state.nested_parse(self.content, self.content_offset,
admonition_node)
# 返回最终的警告提示节点列表
return [admonition_node]
# 为 Sphinx 应用程序设置函数,用于配置各种选项和插件
def setup(app):
# 添加一个配置值,用于 `ifconfig` 指令
app.add_config_value('python_version_major', str(sys.version_info.major), 'env')
# 添加一个词法分析器,将 'NumPyC' 识别为 NumPyLexer 类型
app.add_lexer('NumPyC', NumPyLexer)
# 添加一个自定义指令 'legacy',使用 LegacyDirective 处理
app.add_directive("legacy", LegacyDirective)
# 将 'numpy.char' 的模块别名设定为 numpy.char
# 虽然对象类型是 `module`,但名称是模块的别名,用于使 Sphinx 可以识别这些别名作为真实模块
sys.modules['numpy.char'] = numpy.char
# -----------------------------------------------------------------------------
# HTML 输出配置
# -----------------------------------------------------------------------------
# 设置 HTML 主题
html_theme = 'pydata_sphinx_theme'
# 设置网站图标路径
html_favicon = '_static/favicon/favicon.ico'
# 设置版本切换器,versions.json 存储在文档仓库中
if os.environ.get('CIRCLE_JOB', False) and \
os.environ.get('CIRCLE_BRANCH', '') != 'main':
# 对于 PR,将版本名称设置为其引用
switcher_version = os.environ['CIRCLE_BRANCH']
elif ".dev" in version:
switcher_version = "devdocs"
else:
switcher_version = f"{version}"
# 设置 HTML 主题选项
html_theme_options = {
"logo": {
"image_light": "_static/numpylogo.svg",
"image_dark": "_static/numpylogo_dark.svg",
},
"github_url": "https://github.com/numpy/numpy",
"collapse_navigation": True,
"external_links": [
{"name": "Learn", "url": "https://numpy.org/numpy-tutorials/"},
{"name": "NEPs", "url": "https://numpy.org/neps"},
],
"header_links_before_dropdown": 6,
# 添加亮色/暗色模式和文档版本切换器
"navbar_end": [
"search-button",
"theme-switcher",
"version-switcher",
"navbar-icon-links"
],
"navbar_persistent": [],
"switcher": {
"version_match": switcher_version,
"json_url": "https://numpy.org/doc/_static/versions.json",
},
"show_version_warning_banner": True,
}
# 设置 HTML 页面标题
html_title = "%s v%s Manual" % (project, version)
# 设置静态文件路径
html_static_path = ['_static']
# 设置最后更新时间格式
html_last_updated_fmt = '%b %d, %Y'
# 设置 HTML 使用的 CSS 文件
html_css_files = ["numpy.css"]
# 设置 HTML 上下文
html_context = {"default_mode": "light"}
# 禁用模块索引
html_use_modindex = True
# 不复制源文件
html_copy_source = False
# 禁用文档索引
html_domain_indices = False
# 设置 HTML 文件后缀
html_file_suffix = '.html'
# 设置 HTML 帮助文档的基本名称
htmlhelp_basename = 'numpy'
# 如果扩展列表中包含 'sphinx.ext.pngmath'
if 'sphinx.ext.pngmath' in extensions:
# 启用 PNG 数学公式预览
pngmath_use_preview = True
# 设置 dvipng 参数
pngmath_dvipng_args = ['-gamma', '1.5', '-D', '96', '-bg', 'Transparent']
# -----------------------------------------------------------------------------
# LaTeX 输出配置
# -----------------------------------------------------------------------------
# 设置纸张大小('letter' 或 'a4')
#latex_paper_size = 'letter'
# 设置字体大小('10pt', '11pt' 或 '12pt')
#latex_font_size = '10pt'
# 设置 LaTeX 引擎为 XeLaTeX,以支持更好的 Unicode 字符处理能力
latex_engine = 'xelatex'
# 将文档树分组为 LaTeX 文件的列表。每个元组包含:
# (源文件的起始点,目标文件名,标题,作者,文档类别[如何指南/手册])
_stdauthor = 'Written by the NumPy community'
latex_documents = [
('reference/index', 'numpy-ref.tex', 'NumPy Reference',
_stdauthor, 'manual'),
('user/index', 'numpy-user.tex', 'NumPy User Guide',
_stdauthor, 'manual'),
]
# 标题页顶部放置的图像文件名(相对于当前目录)
#latex_logo = None
# 对于“手册”文档,如果为 True,则顶级标题为部分而不是章节
#latex_use_parts = False
latex_elements = {
}
# LaTeX 导言区的附加内容
latex_elements['preamble'] = r'''
\newfontfamily\FontForChinese{FandolSong-Regular}[Extension=.otf]
\catcode`琴\active\protected\def琴{{\FontForChinese\string琴}}
\catcode`春\active\protected\def春{{\FontForChinese\string春}}
\catcode`鈴\active\protected\def鈴{{\FontForChinese\string鈴}}
\catcode`猫\active\protected\def猫{{\FontForChinese\string猫}}
\catcode`傅\active\protected\def傅{{\FontForChinese\string傅}}
\catcode`立\active\protected\def立{{\FontForChinese\string立}}
\catcode`业\active\protected\def业{{\FontForChinese\string业}}
\catcode`(\active\protected\def({{\FontForChinese\string(}}
\catcode`)\active\protected\def){{\FontForChinese\string)}}
% 在参数部分的标题后面放置一个换行。这在 Sphinx 5.0.0+ 中是默认行为,因此不再需要旧的 hack。
% 不幸的是,sphinx.sty 5.0.0 没有更新其版本日期,因此我们检查 sphinxpackagefootnote.sty(自 Sphinx 4.0.0 起存在)。
\makeatletter
\@ifpackagelater{sphinxpackagefootnote}{2022/02/12}
{}% Sphinx >= 5.0.0,无需操作
{%
\usepackage{expdlist}
\let\latexdescription=\description
\def\description{\latexdescription{}{} \breaklabel}
% 修复 expdlist 旧 LaTeX 包的问题:
% 1) 移除额外的空格
\usepackage{etoolbox}
\patchcmd\@item{{\@breaklabel} }{{\@breaklabel}}{}{}
% 2) 修复 expdlist 在长标签后换行的 bug
\def\breaklabel{%
\def\@breaklabel{%
\leavevmode\par
% 现在是一个 hack,因为 Sphinx 在术语节点之后插入 \leavevmode
\def\leavevmode{\def\leavevmode{\unhbox\voidb@x}}%
}%
}
}% Sphinx < 5.0.0(假设 >= 4.0.0)
\makeatother
% 使示例等部分的标题更小更紧凑
\makeatletter
\titleformat{\paragraph}{\normalsize\py@HeaderFamily}%
{\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor}
\titlespacing*{\paragraph}{0pt}{1ex}{0pt}
\makeatother
% 修复页眉页脚
\renewcommand{\chaptermark}[1]{\markboth{\MakeUppercase{\thechapter.\ #1}}{}}
\renewcommand{\sectionmark}[1]{\markright{\MakeUppercase{\thesection.\ #1}}}
'''
# 将文档附加为所有手册的附录
#latex_appendices = []
# 如果为 False,则不生成模块索引
latex_use_modindex = False
# -----------------------------------------------------------------------------
# Texinfo output
# -----------------------------------------------------------------------------
# 定义生成 Texinfo 格式文档的配置参数
texinfo_documents = [
("index", 'numpy', 'NumPy Documentation', _stdauthor, 'NumPy',
"NumPy: array processing for numbers, strings, records, and objects.",
'Programming',
1),
]
# -----------------------------------------------------------------------------
# Intersphinx configuration
# -----------------------------------------------------------------------------
# 定义用于 intersphinx 的映射配置,指定外部文档的链接
intersphinx_mapping = {
'neps': ('https://numpy.org/neps', None),
'python': ('https://docs.python.org/3', None),
'scipy': ('https://docs.scipy.org/doc/scipy', None),
'matplotlib': ('https://matplotlib.org/stable', None),
'imageio': ('https://imageio.readthedocs.io/en/stable', None),
'skimage': ('https://scikit-image.org/docs/stable', None),
'pandas': ('https://pandas.pydata.org/pandas-docs/stable', None),
'scipy-lecture-notes': ('https://scipy-lectures.org', None),
'pytest': ('https://docs.pytest.org/en/stable', None),
'numpy-tutorials': ('https://numpy.org/numpy-tutorials', None),
'numpydoc': ('https://numpydoc.readthedocs.io/en/latest', None),
'dlpack': ('https://dmlc.github.io/dlpack/latest', None)
}
# -----------------------------------------------------------------------------
# NumPy extensions
# -----------------------------------------------------------------------------
# 定义 NumPy 扩展功能的配置选项
# 指定进行虚拟导入的 XML 文件
phantom_import_file = 'dump.xml'
# 设置 numpydoc 是否生成示例部分的图表
numpydoc_use_plots = True
# -----------------------------------------------------------------------------
# Autosummary
# -----------------------------------------------------------------------------
# 自动生成摘要页面的配置开关
autosummary_generate = True
# -----------------------------------------------------------------------------
# Coverage checker
# -----------------------------------------------------------------------------
# 覆盖率检查工具的配置选项
# 忽略的模块列表
coverage_ignore_modules = r"""
""".split()
# 忽略的函数列表的正则表达式
coverage_ignore_functions = r"""
test($|_) (some|all)true bitwise_not cumproduct pkgload
generic\.
""".split()
# 忽略的类列表
coverage_ignore_classes = r"""
""".split()
# C 语言覆盖检查路径
coverage_c_path = []
# C 语言覆盖检查正则表达式
coverage_c_regexes = {}
# 忽略的 C 语言项
coverage_ignore_c_items = {}
# -----------------------------------------------------------------------------
# Plots
# -----------------------------------------------------------------------------
# 绘图配置选项
# 绘图前的预处理代码
plot_pre_code = """
import numpy as np
np.random.seed(0)
"""
# 是否包含绘图源代码
plot_include_source = True
# 绘图格式及其分辨率设置
plot_formats = [('png', 100), 'pdf']
# 绘图参数配置
import math
phi = (math.sqrt(5) + 1)/2
plot_rcparams = {
'font.size': 8,
'axes.titlesize': 8,
'axes.labelsize': 8,
'xtick.labelsize': 8,
'ytick.labelsize': 8,
'legend.fontsize': 8,
'figure.figsize': (3*phi, 3),
'figure.subplot.bottom': 0.2,
'figure.subplot.left': 0.2,
'figure.subplot.right': 0.9,
}
# 设置图形的顶部子图位置为0.85
'figure.subplot.top': 0.85,
# 设置子图之间的水平间距为0.4
'figure.subplot.wspace': 0.4,
# 禁用使用LaTeX渲染文本
'text.usetex': False,
}
# -----------------------------------------------------------------------------
# Source code links
# -----------------------------------------------------------------------------
import inspect # 导入 inspect 模块,用于检查和分析 Python 对象
from os.path import relpath, dirname # 从 os.path 模块中导入 relpath 和 dirname 函数
# 循环遍历包含字符串列表,尝试导入指定的模块,如果成功则将模块名添加到 extensions 列表中
for name in ['sphinx.ext.linkcode', 'numpydoc.linkcode']:
try:
__import__(name)
extensions.append(name) # 将成功导入的模块名添加到 extensions 列表
break # 如果成功导入模块,则结束循环
except ImportError:
pass
else:
print("NOTE: linkcode extension not found -- no links to source generated") # 如果未找到任何 linkcode 扩展模块,则打印提示信息
# 定义一个函数,根据输入的对象返回相应的 C 源文件路径
def _get_c_source_file(obj):
if issubclass(obj, numpy.generic):
return r"_core/src/multiarray/scalartypes.c.src" # 如果输入对象是 numpy.generic 的子类,返回对应的 C 源文件路径
elif obj is numpy.ndarray:
return r"_core/src/multiarray/arrayobject.c" # 如果输入对象是 numpy.ndarray 类型,则返回对应的 C 源文件路径
else:
# 如果输入对象不符合以上条件,返回 None,并提醒需要找到更好的生成方式
return None
def linkcode_resolve(domain, info):
"""
Determine the URL corresponding to Python object
"""
if domain != 'py':
return None # 如果域不是 'py',返回 None
modname = info['module'] # 获取模块名
fullname = info['fullname'] # 获取完整的对象名(包括模块名和对象名)
submod = sys.modules.get(modname) # 获取模块对象
if submod is None:
return None # 如果未找到模块对象,返回 None
obj = submod
for part in fullname.split('.'):
try:
obj = getattr(obj, part) # 逐级获取对象的属性
except Exception:
return None # 如果获取属性时发生异常,返回 None
# 尝试去掉装饰器,这可能是 inspect.getsourcefile 的一个问题
try:
unwrap = inspect.unwrap
except AttributeError:
pass
else:
obj = unwrap(obj)
fn = None # 初始化文件名变量为 None
lineno = None # 初始化行号变量为 None
# 尝试链接 C 扩展类型
if isinstance(obj, type) and obj.__module__ == 'numpy':
fn = _get_c_source_file(obj) # 获取对应的 C 源文件路径
if fn is None:
try:
fn = inspect.getsourcefile(obj) # 尝试获取对象的源文件路径
except Exception:
fn = None
if not fn:
return None # 如果未获取到源文件路径,返回 None
# 忽略重新导出的对象,因为它们的源文件不在 numpy 仓库内
module = inspect.getmodule(obj)
if module is not None and not module.__name__.startswith("numpy"):
return None # 如果模块不是以 'numpy' 开头,返回 None
try:
source, lineno = inspect.getsourcelines(obj) # 获取对象的源码和起始行号
except Exception:
lineno = None
fn = relpath(fn, start=dirname(numpy.__file__)) # 获取相对于 numpy 包的路径
if lineno:
linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) # 构造行号范围字符串
else:
linespec = ""
if 'dev' in numpy.__version__:
return "https://github.com/numpy/numpy/blob/main/numpy/%s%s" % (
fn, linespec) # 返回 GitHub 上对应源码的 URL
else:
return "https://github.com/numpy/numpy/blob/v%s/numpy/%s%s" % (
numpy.__version__, fn, linespec) # 返回 GitHub 上对应版本源码的 URL
from pygments.lexers import CLexer # 从 pygments 库导入 CLexer 类
from pygments.lexer import inherit, bygroups # 从 pygments 库导入 inherit 和 bygroups 函数
from pygments.token import Comment # 从 pygments 库导入 Comment 类
class NumPyLexer(CLexer):
name = 'NUMPYLEXER' # 定义自定义的词法分析器名称为 'NUMPYLEXER'
tokens = {
'statements': [
(r'@[a-zA-Z_]*@', Comment.Preproc, 'macro'), # 定义预处理指令的词法规则
inherit, # 继承默认的词法规则
],
}
# -----------------------------------------------------------------------------
# Breathe & Doxygen
# -----------------------------------------------------------------------------
# 定义一个字典,用于指定 Breathe 文档生成工具的项目配置,
# 将 "numpy" 项目的 XML 文档路径设置为相对路径 "../build/doxygen/xml"
breathe_projects = dict(numpy=os.path.join("..", "build", "doxygen", "xml"))
# 设置默认的 Breathe 项目为 "numpy"
breathe_default_project = "numpy"
# 设置默认的成员过滤器,包括 "members", "undoc-members", "protected-members"
breathe_default_members = ("members", "undoc-members", "protected-members")
# See https://github.com/breathe-doc/breathe/issues/696
# 忽略特定的 Doxygen 错误或警告,这里列出了需要忽略的标识符列表
nitpick_ignore = [
('c:identifier', 'FILE'),
('c:identifier', 'size_t'),
('c:identifier', 'PyHeapTypeObject'),
]
.\numpy\doc\source\dev\examples\doxy_class.hpp
/**
* Template to represent limbo numbers.
*
* Specializations for integer types that are part of nowhere.
* It doesn't support with any real types.
*
* @param Tp Type of the integer. Required to be an integer type.
* @param N Number of elements.
*/
template<typename Tp, std::size_t N>
class DoxyLimbo {
public:
/// Default constructor. Initialize nothing.
DoxyLimbo();
/// Set Default behavior for copy the limbo.
DoxyLimbo(const DoxyLimbo<Tp, N> &l);
/// Returns the raw data for the limbo.
const Tp *data();
protected:
Tp p_data[N]; ///< Example for inline comment.
};
.\numpy\doc\source\dev\examples\doxy_func.h
/**
* This a simple brief.
*
* And the details goes here.
* Multi lines are welcome.
*
* @param num leave a comment for parameter num.
* Specify the purpose or expected value range if applicable.
* @param str leave a comment for the second parameter.
* Describe its significance or expected format if applicable.
* @return leave a comment for the returned value.
* Clarify what the function returns, its type, and possible values.
*/
int doxy_javadoc_example(int num, const char *str);
.\numpy\doc\source\dev\examples\doxy_rst.h
/**
* A comment block contains reST markup.
* @rst
* .. note::
*
* Thanks to Breathe_, we were able to bring it to Doxygen_
*
* Some code example::
*
* int example(int x) {
* return x * 2;
* }
* @endrst
*/
void doxy_reST_example(void);
.\numpy\doc\source\f2py\code\setup_example.py
# 从 numpy.distutils.core 模块导入 Extension 类
from numpy.distutils.core import Extension
# 创建名为 ext1 的 Extension 对象,指定名称为 'scalar',源文件为 'scalar.f'
ext1 = Extension(name = 'scalar',
sources = ['scalar.f'])
# 创建名为 ext2 的 Extension 对象,指定名称为 'fib2',源文件为 'fib2.pyf' 和 'fib1.f'
ext2 = Extension(name = 'fib2',
sources = ['fib2.pyf', 'fib1.f'])
# 如果当前脚本被直接执行
if __name__ == "__main__":
# 从 numpy.distutils.core 模块导入 setup 函数
from numpy.distutils.core import setup
# 调用 setup 函数,设置项目名称为 'f2py_example',描述为 "F2PY Users Guide examples"
# 作者为 "Pearu Peterson",作者邮箱为 "pearu@cens.ioc.ee",扩展模块为 [ext1, ext2]
setup(name = 'f2py_example',
description = "F2PY Users Guide examples",
author = "Pearu Peterson",
author_email = "pearu@cens.ioc.ee",
ext_modules = [ext1, ext2]
)
# End of setup_example.py
.\numpy\doc\source\f2py\code\setup_skbuild.py
# 导入 skbuild 库中的 setup 函数,用于配置和安装 Python 包
from skbuild import setup
# 调用 setup 函数,配置和安装一个 Python 包
setup(
# 包的名称为 "fibby"
name="fibby",
# 包的版本号为 "0.0.1"
version="0.0.1",
# 包的描述信息为 "a minimal example package (fortran version)"
description="a minimal example package (fortran version)",
# 包的许可证为 MIT 许可证
license="MIT",
# 指定要包含的包列表,这里包含名为 'fibby' 的包
packages=['fibby'],
# 指定 Python 的最低版本要求为 3.7 及以上
python_requires=">=3.7",
)
.\numpy\doc\source\reference\random\examples\cython\extending.pyx
# extending.pyx 文件的路径
-------------
# 包含外部的文件 extending.pyx,路径相对当前文件的位置
.. include:: ../../../../../../numpy/random/examples/extending.pyx
.\numpy\doc\source\reference\random\performance.py
from timeit import repeat # 导入计时器函数
import pandas as pd # 导入 pandas 库
import numpy as np # 导入 numpy 库
from numpy.random import MT19937, PCG64, PCG64DXSM, Philox, SFC64 # 从 numpy.random 导入多个随机数生成器类
PRNGS = [MT19937, PCG64, PCG64DXSM, Philox, SFC64] # 定义随机数生成器类列表
funcs = {} # 初始化空字典 funcs 用于存储函数调用字符串
integers = 'integers(0, 2**{bits},size=1000000, dtype="uint{bits}")' # 定义整数生成函数模板
funcs['32-bit Unsigned Ints'] = integers.format(bits=32) # 添加生成 32 位无符号整数的函数调用字符串
funcs['64-bit Unsigned Ints'] = integers.format(bits=64) # 添加生成 64 位无符号整数的函数调用字符串
funcs['Uniforms'] = 'random(size=1000000)' # 添加生成均匀分布随机数的函数调用字符串
funcs['Normals'] = 'standard_normal(size=1000000)' # 添加生成标准正态分布随机数的函数调用字符串
funcs['Exponentials'] = 'standard_exponential(size=1000000)' # 添加生成指数分布随机数的函数调用字符串
funcs['Gammas'] = 'standard_gamma(3.0,size=1000000)' # 添加生成 Gamma 分布随机数的函数调用字符串
funcs['Binomials'] = 'binomial(9, .1, size=1000000)' # 添加生成二项分布随机数的函数调用字符串
funcs['Laplaces'] = 'laplace(size=1000000)' # 添加生成拉普拉斯分布随机数的函数调用字符串
funcs['Poissons'] = 'poisson(3.0, size=1000000)' # 添加生成泊松分布随机数的函数调用字符串
setup = """
from numpy.random import {prng}, Generator
rg = Generator({prng}())
""" # 定义设置字符串模板,用于设置随机数生成器
test = "rg.{func}" # 定义测试字符串模板,用于测试随机数生成器性能
table = {} # 初始化空字典 table 用于存储测试结果表格
for prng in PRNGS: # 遍历每个随机数生成器类
print(prng) # 打印当前随机数生成器类的名称
col = {} # 初始化空字典 col 用于存储每种函数调用的最小执行时间
for key in funcs: # 遍历每种函数调用字符串
t = repeat(test.format(func=funcs[key]), # 测试当前函数调用的执行时间
setup.format(prng=prng().__class__.__name__), # 设置随机数生成器
number=1, repeat=3) # 设置测试参数
col[key] = 1000 * min(t) # 将最小执行时间(毫秒)添加到 col 字典中
col = pd.Series(col) # 将 col 字典转换为 pandas Series 对象
table[prng().__class__.__name__] = col # 将当前随机数生成器类的测试结果添加到 table 字典中
npfuncs = {} # 初始化空字典 npfuncs 用于存储 numpy 函数调用字符串
npfuncs.update(funcs) # 将 funcs 字典中的内容复制到 npfuncs 字典中
npfuncs['32-bit Unsigned Ints'] = 'randint(2**32,dtype="uint32",size=1000000)' # 替换生成 32 位无符号整数的函数调用字符串
npfuncs['64-bit Unsigned Ints'] = 'randint(2**64,dtype="uint64",size=1000000)' # 替换生成 64 位无符号整数的函数调用字符串
setup = """
from numpy.random import RandomState
rg = RandomState()
""" # 更新设置字符串模板,用于设置随机数生成器
col = {} # 初始化空字典 col 用于存储每种函数调用的最小执行时间
for key in npfuncs: # 遍历每种函数调用字符串
t = repeat(test.format(func=npfuncs[key]), # 测试当前函数调用的执行时间
setup.format(prng=prng().__class__.__name__), # 设置随机数生成器
number=1, repeat=3) # 设置测试参数
col[key] = 1000 * min(t) # 将最小执行时间(毫秒)添加到 col 字典中
table['RandomState'] = pd.Series(col) # 将 RandomState 类的测试结果添加到 table 字典中
columns = ['MT19937', 'PCG64', 'PCG64DXSM', 'Philox', 'SFC64', 'RandomState'] # 定义表格的列名列表
table = pd.DataFrame(table) # 将 table 字典转换为 pandas DataFrame 对象
order = np.log(table).mean().sort_values().index # 对表格进行排序,按照均值对数值排序
table = table.T # 转置表格
table = table.reindex(columns) # 根据指定列名重新索引表格
table = table.T # 再次转置表格
table = table.reindex([k for k in funcs], axis=0) # 根据函数调用字符串的键重新索引表格
print(table.to_csv(float_format='%0.1f')) # 将表格输出为 CSV 格式,保留一位小数
rel = table.loc[:, ['RandomState']].values @ np.ones( # 计算相对性能比率
(1, table.shape[1])) / table # 计算比率
rel.pop('RandomState') # 删除 RandomState 列
rel = rel.T # 转置比率表格
rel['Overall'] = np.exp(np.log(rel).mean(1)) # 计算平均对数值,再取指数,作为 Overall 列
rel *= 100 # 将比率转换为百分比
rel = np.round(rel) # 四舍五入到整数
rel = rel.T # 再次转置比率表格
print(rel.to_csv(float_format='%0d')) # 将比率表格输出为 CSV 格式,保留整数
# Cross-platform table
rows = ['32-bit Unsigned Ints','64-bit Unsigned Ints','Uniforms','Normals','Exponentials'] # 定义跨平台表格的行名列表
xplat = rel.reindex(rows, axis=0) # 根据指定行名重新索引比率表格
xplat = 100 * (xplat / xplat.MT19937.values[:,None]) # 计算相对于 MT19937 的百分比
overall = np.exp(np.log(xplat).mean(0)) # 计算平均对数值,再取指数,作为 Overall 行
xplat = xplat.T.copy() # 转置并复制比率表格
xplat['Overall']=overall # 添加 Overall 行
print(xplat.T.round(1)) # 将跨平台表格输出为 CSV 格式,保留一位小数
.\numpy\doc\source\reference\simd\gen_features.py
"""
Generate CPU features tables from CCompilerOpt
"""
# 导入必要的模块和类
from os import sys, path
from numpy.distutils.ccompiler_opt import CCompilerOpt
class FakeCCompilerOpt(CCompilerOpt):
# 禁用缓存,因为不需要
conf_nocache = True
def __init__(self, arch, cc, *args, **kwargs):
# 初始化虚拟编译器信息
self.fake_info = (arch, cc, '')
# 调用父类的初始化方法
CCompilerOpt.__init__(self, None, **kwargs)
def dist_compile(self, sources, flags, **kwargs):
# 编译源文件,简化返回所有源文件
return sources
def dist_info(self):
# 返回虚拟编译器信息
return self.fake_info
@staticmethod
def dist_log(*args, stderr=False):
# 静态方法,用于记录日志,这里是避免打印
pass
def feature_test(self, name, force_flags=None, macros=[]):
# 进行特性测试,假设总是返回 True,用于加速
return True
class Features:
def __init__(self, arch, cc):
# 初始化特性对象,使用自定义的虚拟编译器选项
self.copt = FakeCCompilerOpt(arch, cc, cpu_baseline="max")
def names(self):
# 返回CPU基线名称列表
return self.copt.cpu_baseline_names()
def serialize(self, features_names):
# 序列化特性信息
result = []
for f in self.copt.feature_sorted(features_names):
gather = self.copt.feature_supported.get(f, {}).get("group", [])
implies = self.copt.feature_sorted(self.copt.feature_implies(f))
result.append((f, implies, gather))
return result
def table(self, **kwargs):
# 生成特性表格
return self.gen_table(self.serialize(self.names()), **kwargs)
def table_diff(self, vs, **kwargs):
# 比较两个特性对象之间的差异
fnames = set(self.names())
fnames_vs = set(vs.names())
common = fnames.intersection(fnames_vs)
extra = fnames.difference(fnames_vs)
notavl = fnames_vs.difference(fnames)
iextra = {}
inotavl = {}
idiff = set()
for f in common:
implies = self.copt.feature_implies(f)
implies_vs = vs.copt.feature_implies(f)
e = implies.difference(implies_vs)
i = implies_vs.difference(implies)
if not i and not e:
continue
if e:
iextra[f] = e
if i:
inotavl[f] = e
idiff.add(f)
def fbold(f):
# 根据特性是否在extra或notavl集合中返回不同的格式
if f in extra:
return f':enabled:`{f}`'
if f in notavl:
return f':disabled:`{f}`'
return f
def fbold_implies(f, i):
# 根据特性是否在iextra或inotavl集合中返回不同的格式
if i in iextra.get(f, {}):
return f':enabled:`{i}`'
if f in notavl or i in inotavl.get(f, {}):
return f':disabled:`{i}`'
return i
# 将所有差异特性序列化并生成特性表格
diff_all = self.serialize(idiff.union(extra))
diff_all += vs.serialize(notavl)
content = self.gen_table(
diff_all, fstyle=fbold, fstyle_implies=fbold_implies, **kwargs
)
return content
# 生成一个表格的函数,接受多个参数,其中 serialized_features 是序列化特征的列表,
# fstyle 是一个函数,默认为 lambda 表达式,用于格式化特征名字
# fstyle_implies 也是一个函数,默认为 lambda 表达式,用于格式化特征暗示
# **kwargs 是额外的关键字参数
def gen_table(self, serialized_features, fstyle=None, fstyle_implies=None,
**kwargs):
# 如果 fstyle 没有提供,使用默认的 lambda 函数格式化特征名字
if fstyle is None:
fstyle = lambda ft: f'``{ft}``'
# 如果 fstyle_implies 没有提供,使用默认的 lambda 函数格式化特征暗示
if fstyle_implies is None:
fstyle_implies = lambda origin, ft: fstyle(ft)
# 初始化空列表 rows 用于存储表格的每一行
rows = []
# 初始化标志 have_gather,用于标记是否存在 gather 类型的特征
have_gather = False
# 遍历 serialized_features 中的每个元素 (f, implies, gather)
for f, implies, gather in serialized_features:
# 如果 gather 为真值(非空),则设置 have_gather 为 True
if gather:
have_gather = True
# 使用 fstyle 函数格式化特征名字 f
name = fstyle(f)
# 使用 fstyle_implies 函数格式化 implies 列表中的每个元素
implies = ' '.join([fstyle_implies(f, i) for i in implies])
# 使用 fstyle_implies 函数格式化 gather 列表中的每个元素
gather = ' '.join([fstyle_implies(f, i) for i in gather])
# 将格式化后的 (name, implies, gather) 添加到 rows 中
rows.append((name, implies, gather))
# 如果 rows 列表为空,则返回空字符串
if not rows:
return ''
# 初始化字段列表 fields
fields = ["Name", "Implies", "Gathers"]
# 如果没有 gather 类型的特征,删除 fields 中的最后一个元素
if not have_gather:
del fields[2]
# 更新 rows,只保留 name 和 implies 两列
rows = [(name, implies) for name, implies, _ in rows]
# 调用 gen_rst_table 方法生成并返回一个 reStructuredText 格式的表格
return self.gen_rst_table(fields, rows, **kwargs)
# 生成 reStructuredText 格式的表格,接受字段名和行数据
def gen_rst_table(self, field_names, rows, tab_size=4):
# 断言条件:如果 rows 为空或者 field_names 的长度等于 rows 的第一行的长度
assert(not rows or len(field_names) == len(rows[0]))
# 在 rows 中添加 field_names 作为表格的首行
rows.append(field_names)
# 计算字段的长度列表
fld_len = len(field_names)
cls_len = [max(len(c[i]) for c in rows) for i in range(fld_len)]
# 根据字段长度列表生成表格的边框
cformat = ' '.join('{:<%d}' % i for i in cls_len)
border = cformat.format(*['='*i for i in cls_len])
# 对每一行数据进行格式化
rows = [cformat.format(*row) for row in rows]
# 添加表格的头部和底部边框
rows = [border, cformat.format(*field_names), border] + rows
# 添加表格的底部边框
rows += [border]
# 在每一行数据前添加指定大小的左边距
rows = [(' ' * tab_size) + r for r in rows]
# 返回格式化后的表格内容,使用换行符连接每一行
return '\n'.join(rows)
# 定义一个函数,生成包含标题和内容的文本段落,内容用表格格式化
def wrapper_section(title, content, tab_size=4):
tab = ' '*tab_size
# 如果内容不为空,则生成带标题的文本段落
if content:
return (
f"{title}\n{'~'*len(title)}" # 标题及其下方的波浪线
f"\n.. table::\n{tab}:align: left\n\n" # 开始定义表格
f"{content}\n\n" # 添加表格内容
)
return '' # 内容为空时返回空字符串
# 定义一个函数,生成包含标题和表格的标签页
def wrapper_tab(title, table, tab_size=4):
tab = ' '*tab_size
# 如果表格不为空,则生成包含标题和表格的标签页
if table:
('\n' + tab).join((
'.. tab:: ' + title, # 标签页标题
tab + '.. table::', # 定义表格
tab + 'align: left', # 设置表格对齐方式
table + '\n\n' # 添加表格内容
))
return '' # 表格为空时返回空字符串
# 主程序入口
if __name__ == '__main__':
# 美化后的架构名称映射表
pretty_names = {
"PPC64": "IBM/POWER big-endian",
"PPC64LE": "IBM/POWER little-endian",
"S390X": "IBM/ZSYSTEM(S390X)",
"ARMHF": "ARMv7/A32",
"AARCH64": "ARMv8/A64",
"ICC": "Intel Compiler",
# "ICCW": "Intel Compiler msvc-like",
"MSVC": "Microsoft Visual C/C++"
}
# 生成路径:当前脚本所在目录下的generated_tables文件夹
gen_path = path.join(
path.dirname(path.realpath(__file__)), "generated_tables"
)
# 打开并写入cpu_features.inc文件
with open(path.join(gen_path, 'cpu_features.inc'), 'w') as fd:
fd.write(f'.. generated via {__file__}\n\n') # 写入生成信息
# 遍历架构列表生成表格段落
for arch in (
("x86", "PPC64", "PPC64LE", "ARMHF", "AARCH64", "S390X")
):
title = "On " + pretty_names.get(arch, arch) # 获取美化后的架构名称
table = Features(arch, 'gcc').table() # 调用Features类生成表格内容
fd.write(wrapper_section(title, table)) # 调用wrapper_section生成段落并写入文件
# 打开并写入compilers-diff.inc文件
with open(path.join(gen_path, 'compilers-diff.inc'), 'w') as fd:
fd.write(f'.. generated via {__file__}\n\n') # 写入生成信息
# 遍历架构和编译器对生成表格段落
for arch, cc_names in (
("x86", ("clang", "ICC", "MSVC")),
("PPC64", ("clang",)),
("PPC64LE", ("clang",)),
("ARMHF", ("clang",)),
("AARCH64", ("clang",)),
("S390X", ("clang",))
):
arch_pname = pretty_names.get(arch, arch) # 获取美化后的架构名称
# 遍历编译器列表生成表格段落
for cc in cc_names:
title = f"On {arch_pname}::{pretty_names.get(cc, cc)}" # 构造标题
# 调用Features类生成表格差异内容
table = Features(arch, cc).table_diff(Features(arch, "gcc"))
fd.write(wrapper_section(title, table)) # 调用wrapper_section生成段落并写入文件
.\numpy\doc\source\user\conftest.py
# 从 numpy 的 conftest 模块导入 dt_config 对象,用于 doctesting 配置
from numpy.conftest import dt_config # noqa: F401
# 导入的 dt_config 对象未直接使用,但通过 noqa: F401 标记避免未使用的警告
# 如果需要设置断点调试,可以取消注释下一行代码
# breakpoint()
.\numpy\doc\source\user\plots\matplotlib1.py
# 导入 matplotlib.pyplot 模块,并简写为 plt
import matplotlib.pyplot as plt
# 导入 numpy 模块,并简写为 np
import numpy as np
# 创建一个包含指定数据的 NumPy 数组
a = np.array([2, 1, 5, 7, 4, 6, 8, 14, 10, 9, 18, 20, 22])
# 使用 matplotlib.pyplot 的 plot 函数绘制 a 数组的折线图
plt.plot(a)
# 显示绘制的图形
plt.show()
.\numpy\doc\source\user\plots\matplotlib2.py
import matplotlib.pyplot as plt # 导入matplotlib的绘图模块,命名为plt
import numpy as np # 导入numpy数值计算库,命名为np
x = np.linspace(0, 5, 20) # 在0到5之间生成20个等间距的数值,作为x轴数据
y = np.linspace(0, 10, 20) # 在0到10之间生成20个等间距的数值,作为y轴数据
plt.plot(x, y, 'purple') # 绘制以(x, y)为坐标的紫色线条,用于展示连接的数据点
plt.plot(x, y, 'o') # 绘制以(x, y)为坐标的散点,用圆圈表示每个数据点
plt.show() # 显示图形,展示绘制的图像
.\numpy\doc\source\user\plots\matplotlib3.py
# 导入 NumPy 库,通常用于处理数组和数值计算
import numpy as np
# 导入 Matplotlib 的 pyplot 模块,用于绘图操作
import matplotlib.pyplot as plt
# 创建一个新的图形对象
fig = plt.figure()
# 在图形对象上添加一个三维坐标系的子图
ax = fig.add_subplot(projection='3d')
# 生成一个 X 轴上的坐标数组,范围从 -5 到 5,步长为 0.15
X = np.arange(-5, 5, 0.15)
# 生成一个 Y 轴上的坐标数组,范围从 -5 到 5,步长为 0.15
Y = np.arange(-5, 5, 0.15)
# 将 X 和 Y 坐标数组转换成网格形式
X, Y = np.meshgrid(X, Y)
# 计算每个网格点的极径 R
R = np.sqrt(X**2 + Y**2)
# 计算每个网格点的 Z 值,使用 sin 函数作为高度值
Z = np.sin(R)
# 在三维坐标系上绘制一个表面图,使用 viridis 颜色映射
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis')
# 显示绘制的图形
plt.show()
.\numpy\doc\source\user\plots\meshgrid_plot.py
# 导入 NumPy 库,用于科学计算
import numpy as np
# 导入 Matplotlib 库中的 pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 创建 NumPy 数组 x 和 y,分别表示 x 和 y 轴上的坐标点
x = np.array([0, 1, 2, 3])
y = np.array([0, 1, 2, 3, 4, 5])
# 使用 meshgrid 函数生成网格数据,xx 和 yy 是网格化后的 x 和 y 值
xx, yy = np.meshgrid(x, y)
# 使用 Matplotlib 的 pyplot 模块绘制散点图
plt.plot(xx, yy, marker='o', color='k', linestyle='none')
.\numpy\numpy\char\__init__.py
# 导入 numpy._core.defchararray 模块中的 __all__ 和 __doc__ 变量
from numpy._core.defchararray import __all__, __doc__
# 导入 numpy._core.defchararray 模块中的所有函数和变量
from numpy._core.defchararray import *
.\numpy\numpy\char\__init__.pyi
# 从 numpy._core.defchararray 模块中导入一系列字符串操作函数,用于操作字符数组
from numpy._core.defchararray import (
equal as equal, # 别名 equal,用于比较两个字符数组是否相等
not_equal as not_equal, # 别名 not_equal,用于比较两个字符数组是否不相等
greater_equal as greater_equal, # 别名 greater_equal,用于比较字符数组左侧是否大于等于右侧
less_equal as less_equal, # 别名 less_equal,用于比较字符数组左侧是否小于等于右侧
greater as greater, # 别名 greater,用于比较字符数组左侧是否大于右侧
less as less, # 别名 less,用于比较字符数组左侧是否小于右侧
str_len as str_len, # 别名 str_len,返回字符数组中每个字符串的长度
add as add, # 别名 add,用于字符数组的逐元素相加
multiply as multiply, # 别名 multiply,用于字符数组的逐元素相乘
mod as mod, # 别名 mod,用于字符数组的逐元素取模运算
capitalize as capitalize, # 别名 capitalize,将字符数组中每个字符串的首字母大写
center as center, # 别名 center,将字符数组中每个字符串居中对齐
count as count, # 别名 count,统计字符数组中每个字符串出现的次数
decode as decode, # 别名 decode,解码字符数组中的每个字符串
encode as encode, # 别名 encode,编码字符数组中的每个字符串
endswith as endswith, # 别名 endswith,判断字符数组中每个字符串是否以指定后缀结尾
expandtabs as expandtabs, # 别名 expandtabs,将字符数组中每个字符串的制表符扩展为空格
find as find, # 别名 find,查找字符数组中每个字符串第一次出现的位置
index as index, # 别名 index,查找字符数组中每个字符串第一次出现的位置
isalnum as isalnum, # 别名 isalnum,判断字符数组中每个字符串是否由字母和数字组成
isalpha as isalpha, # 别名 isalpha,判断字符数组中每个字符串是否只包含字母
isdigit as isdigit, # 别名 isdigit,判断字符数组中每个字符串是否只包含数字
islower as islower, # 别名 islower,判断字符数组中每个字符串是否全为小写字母
isspace as isspace, # 别名 isspace,判断字符数组中每个字符串是否只包含空白字符
istitle as istitle, # 别名 istitle,判断字符数组中每个字符串是否遵循标题化规则
isupper as isupper, # 别名 isupper,判断字符数组中每个字符串是否全为大写字母
join as join, # 别名 join,将字符数组中每个字符串用指定分隔符连接成一个字符串
ljust as ljust, # 别名 ljust,将字符数组中每个字符串左对齐,并用空格填充至指定长度
lower as lower, # 别名 lower,将字符数组中每个字符串转换为小写
lstrip as lstrip, # 别名 lstrip,将字符数组中每个字符串左侧的空白字符删除
partition as partition, # 别名 partition,将字符数组中每个字符串按照第一次出现的分隔符分为三部分
replace as replace, # 别名 replace,将字符数组中每个字符串的指定子串替换为新的子串
rfind as rfind, # 别名 rfind,查找字符数组中每个字符串最后一次出现的位置
rindex as rindex, # 别名 rindex,查找字符数组中每个字符串最后一次出现的位置
rjust as rjust, # 别名 rjust,将字符数组中每个字符串右对齐,并用空格填充至指定长度
rpartition as rpartition, # 别名 rpartition,将字符数组中每个字符串按照最后一次出现的分隔符分为三部分
rsplit as rsplit, # 别名 rsplit,将字符数组中每个字符串按照指定分隔符从右向左分割
rstrip as rstrip, # 别名 rstrip,将字符数组中每个字符串右侧的空白字符删除
split as split, # 别名 split,将字符数组中每个字符串按照指定分隔符分割
splitlines as splitlines, # 别名 splitlines,将字符数组中每个字符串按照行分割
startswith as startswith, # 别名 startswith,判断字符数组中每个字符串是否以指定前缀开头
strip as strip, # 别名 strip,将字符数组中每个字符串两侧的空白字符删除
swapcase as swapcase, # 别名 swapcase,将字符数组中每个字符串的大小写互换
title as title, # 别名 title,将字符数组中每个字符串转换为标题化形式
translate as translate, # 别名 translate,根据字符映射表对字符数组中每个字符串进行转换
upper as upper, # 别名 upper,将字符数组中每个字符串转换为大写
zfill as zfill, # 别名 zfill,将字符数组中每个字符串左侧填充零到指定宽度
isnumeric as isnumeric, # 别名 isnumeric,判断字符数组中每个字符串是否只包含数字字符
isdecimal as isdecimal, # 别名 isdecimal,判断字符数组中每个字符串是否只包含十进制数字字符
array as array, # 别名 array,创建一个字符数组对象
asarray as asarray, # 别名 asarray,将输入转换为字符数组对象
compare_chararrays as compare_chararrays, # 别名 compare_chararrays,比较两个字符数组对象
chararray as chararray # 别名 chararray,字符数组对象类型
)
__all__: list[str] # 定义变量 __all__,包含在 from ... import * 语句中应导入的公共名称列表
.\numpy\numpy\compat\py3k.py
"""
Python 3.X compatibility tools.
While this file was originally intended for Python 2 -> 3 transition,
it is now used to create a compatibility layer between different
minor versions of Python 3.
While the active version of numpy may not support a given version of python, we
allow downstream libraries to continue to use these shims for forward
compatibility with numpy while they transition their code to newer versions of
Python.
"""
__all__ = ['bytes', 'asbytes', 'isfileobj', 'getexception', 'strchar',
'unicode', 'asunicode', 'asbytes_nested', 'asunicode_nested',
'asstr', 'open_latin1', 'long', 'basestring', 'sixu',
'integer_types', 'is_pathlib_path', 'npy_load_module', 'Path',
'pickle', 'contextlib_nullcontext', 'os_fspath', 'os_PathLike']
import sys # 导入 sys 模块,用于访问系统相关功能
import os # 导入 os 模块,用于访问操作系统功能
from pathlib import Path # 导入 Path 类,用于处理路径操作
import io # 导入 io 模块,用于处理文件流操作
try:
import pickle5 as pickle # 尝试导入 pickle5 库,若不成功则导入标准的 pickle 库
except ImportError:
import pickle # 如果导入 pickle5 失败,导入标准的 pickle 库
long = int # 定义 long 为 int 类型,用于兼容 Python 3 中移除的 long 类型
integer_types = (int,) # 定义 integer_types 为包含 int 的元组,用于兼容 Python 2/3 整数类型的差异
basestring = str # 定义 basestring 为 str 类型,用于兼容 Python 2/3 中字符串类型的差异
unicode = str # 定义 unicode 为 str 类型,用于兼容 Python 2/3 中的字符串类型
bytes = bytes # 定义 bytes 为 bytes 类型,用于兼容 Python 2/3 中的字节类型
def asunicode(s):
if isinstance(s, bytes):
return s.decode('latin1') # 如果 s 是 bytes 类型,则解码为 str 类型,使用 Latin-1 编码
return str(s) # 否则直接转换为 str 类型
def asbytes(s):
if isinstance(s, bytes):
return s # 如果 s 已经是 bytes 类型,则直接返回
return str(s).encode('latin1') # 否则将 s 转换为 str 类型后再编码为 bytes 类型,使用 Latin-1 编码
def asstr(s):
if isinstance(s, bytes):
return s.decode('latin1') # 如果 s 是 bytes 类型,则解码为 str 类型,使用 Latin-1 编码
return str(s) # 否则直接转换为 str 类型
def isfileobj(f):
if not isinstance(f, (io.FileIO, io.BufferedReader, io.BufferedWriter)):
return False # 如果 f 不是文件对象类型,则返回 False
try:
f.fileno() # 尝试获取文件描述符,可能会抛出 OSError 异常
return True # 如果成功获取文件描述符,则返回 True
except OSError:
return False # 获取文件描述符失败,返回 False
def open_latin1(filename, mode='r'):
return open(filename, mode=mode, encoding='iso-8859-1') # 使用 Latin-1 编码打开指定文件
def sixu(s):
return s # 返回参数 s,用于兼容性,不进行额外处理
strchar = 'U' # 定义 strchar 为 'U'
def getexception():
return sys.exc_info()[1] # 返回当前异常信息的第一个元素,即异常对象
def asbytes_nested(x):
if hasattr(x, '__iter__') and not isinstance(x, (bytes, unicode)):
return [asbytes_nested(y) for y in x] # 如果 x 是可迭代对象且不是 bytes 或 unicode 类型,则递归处理每个元素
else:
return asbytes(x) # 否则将 x 转换为 bytes 类型并返回
def asunicode_nested(x):
if hasattr(x, '__iter__') and not isinstance(x, (bytes, unicode)):
return [asunicode_nested(y) for y in x] # 如果 x 是可迭代对象且不是 bytes 或 unicode 类型,则递归处理每个元素
else:
return asunicode(x) # 否则将 x 转换为 unicode 类型并返回
def is_pathlib_path(obj):
"""
Check whether obj is a `pathlib.Path` object.
Prefer using ``isinstance(obj, os.PathLike)`` instead of this function.
"""
return isinstance(obj, Path) # 检查 obj 是否为 pathlib.Path 对象
# from Python 3.7
class contextlib_nullcontext:
"""Context manager that does no additional processing.
Used as a stand-in for a normal context manager, when a particular
block of code is only sometimes used with a normal context manager:
cm = optional_cm if condition else nullcontext()
with cm:
# Perform operation, using optional_cm if condition is True
.. note::
Prefer using `contextlib.nullcontext` instead of this context manager.
"""
def __init__(self, enter_result=None):
self.enter_result = enter_result # 初始化上下文管理器,保存进入结果
# 定义上下文管理器的进入方法,当使用 with 语句时执行
def __enter__(self):
# 返回上下文管理器的进入结果,通常是为了与 as 关键字后的变量进行绑定
return self.enter_result
# 定义上下文管理器的退出方法,当退出 with 语句块时执行
def __exit__(self, *excinfo):
# 占位符方法体,不做任何实际操作,即使在异常发生时也不处理异常
pass
# 加载一个模块。使用 ``load_module`` 方法,该方法将在 Python 3.12 中被弃用。
# 另外,可以使用 ``exec_module`` 方法,该方法在 numpy.distutils.misc_util.exec_mod_from_location 中定义。
# .. versionadded:: 1.11.2
# 版本添加说明,从版本 1.11.2 开始可用。
# Parameters
# ----------
# name : str
# 完整的模块名称。
# fn : str
# 模块文件的路径。
# info : tuple, optional
# 仅用于向后兼容 Python 2.*。
# Returns
# -------
# mod : module
# 加载并返回的模块对象。
def npy_load_module(name, fn, info=None):
# 显式延迟导入以避免在启动时导入 importlib 的开销
from importlib.machinery import SourceFileLoader
return SourceFileLoader(name, fn).load_module()
# 将 os.fspath 函数赋值给变量 os_fspath,以便更方便地引用该函数
os_fspath = os.fspath
# 将 os.PathLike 类型赋值给变量 os_PathLike,以便更方便地引用该类型
os_PathLike = os.PathLike
.\numpy\numpy\compat\tests\__init__.py
# 定义一个名为find_duplicate的函数,接收一个参数nums,该参数是一个整数列表
def find_duplicate(nums):
# 创建一个空集合dup_set,用于存储出现过的数字
dup_set = set()
# 遍历nums列表中的每个元素num
for num in nums:
# 如果num已经在dup_set中存在,表示num是重复出现的数字
if num in dup_set:
# 返回找到的重复数字num
return num
# 将num加入dup_set中,记录已经出现的数字
dup_set.add(num)
# 如果没有找到重复数字,返回None
return None
.\numpy\numpy\compat\__init__.py
"""
python
"""
兼容性模块。
此模块包含从 Python 本身或第三方扩展复制的重复代码,可能包含以下原因:
* 兼容性
* 我们可能只需要复制库/模块的一小部分
此模块自 1.26.0 版本起已被弃用,并将在将来的版本中移除。
"""
# 导入警告模块
import warnings
# 从内部工具模块导入 _inspect 模块
from .._utils import _inspect
# 从内部工具模块的 _inspect 模块导入 getargspec 和 formatargspec 函数
from .._utils._inspect import getargspec, formatargspec
# 从 . 模块导入 py3k 模块
from . import py3k
# 从 .py3k 模块导入所有内容
from .py3k import *
# 引发警告,指示 np.compat 在 Python 2 到 3 的转换期间使用,自 1.26.0 版本起已弃用,并将被移除
warnings.warn(
"`np.compat`, which was used during the Python 2 to 3 transition,"
" is deprecated since 1.26.0, and will be removed",
DeprecationWarning, stacklevel=2
)
# 将空列表tion,"
" is deprecated since 1.26.0, and will be removed",
DeprecationWarning, stacklevel=2
)
# 初始化模块的公开接口列表
__all__ = []
# 将 _inspect 模块中定义的所有公开名称添加到 __all__ 中
__all__.extend(_inspect.__all__)
# 将 py3k 模块中定义的所有公开名称添加到 __all__ 中
__all__.extend(py3k.__all__)
.\numpy\numpy\conftest.py
"""
Pytest configuration and fixtures for the Numpy test suite.
"""
# 导入必要的库和模块
import os # 导入操作系统接口
import tempfile # 导入临时文件和目录创建的模块
from contextlib import contextmanager # 导入上下文管理器
import warnings # 导入警告模块
import hypothesis # 导入假设测试库
import pytest # 导入pytest测试框架
import numpy # 导入NumPy库
from numpy._core._multiarray_tests import get_fpu_mode # 导入获取FPU模式的函数
# 尝试导入scipy_doctest.conftest模块,标记是否成功导入
try:
from scipy_doctest.conftest import dt_config
HAVE_SCPDT = True
except ModuleNotFoundError:
HAVE_SCPDT = False
# 初始化旧的FPU模式和结果收集字典
_old_fpu_mode = None
_collect_results = {}
# 设置hypothesis缓存的主目录,使用已知且持久的临时目录
hypothesis.configuration.set_hypothesis_home_dir(
os.path.join(tempfile.gettempdir(), ".hypothesis")
)
# 注册两个自定义的Hypothesis配置文件,用于NumPy测试
hypothesis.settings.register_profile(
name="numpy-profile", deadline=None, print_blob=True,
)
hypothesis.settings.register_profile(
name="np.test() profile",
deadline=None, print_blob=True, database=None, derandomize=True,
suppress_health_check=list(hypothesis.HealthCheck),
)
# 根据pytest.ini文件的存在与否加载默认的Hypothesis配置文件
_pytest_ini = os.path.join(os.path.dirname(__file__), "..", "pytest.ini")
hypothesis.settings.load_profile(
"numpy-profile" if os.path.isfile(_pytest_ini) else "np.test() profile"
)
# 设置NUMPY_EXPERIMENTAL_DTYPE_API环境变量为1,用于_umath_tests
os.environ["NUMPY_EXPERIMENTAL_DTYPE_API"] = "1"
# 定义pytest的配置函数,添加自定义标记
def pytest_configure(config):
config.addinivalue_line("markers",
"valgrind_error: Tests that are known to error under valgrind.")
config.addinivalue_line("markers",
"leaks_references: Tests that are known to leak references.")
config.addinivalue_line("markers",
"slow: Tests that are very slow.")
config.addinivalue_line("markers",
"slow_pypy: Tests that are very slow on pypy.")
# 定义pytest的命令行选项,用于设置可用内存量
def pytest_addoption(parser):
parser.addoption("--available-memory", action="store", default=None,
help=("Set amount of memory available for running the "
"test suite. This can result to tests requiring "
"especially large amounts of memory to be skipped. "
"Equivalent to setting environment variable "
"NPY_AVAILABLE_MEM. Default: determined"
"automatically."))
# 在测试会话开始时,根据命令行选项设置环境变量NPY_AVAILABLE_MEM
def pytest_sessionstart(session):
available_mem = session.config.getoption('available_memory')
if available_mem is not None:
os.environ['NPY_AVAILABLE_MEM'] = available_mem
# TODO: 移除yield测试后修复此函数
@pytest.hookimpl()
def pytest_itemcollected(item):
"""
Check FPU precision mode was not changed during test collection.
"""
The clumsy way we do it here is mainly necessary because numpy
still uses yield tests, which can execute code at test collection
time.
"""
# 声明全局变量 _old_fpu_mode,用于存储旧的浮点数处理单元模式
global _old_fpu_mode
# 获取当前的浮点数处理单元模式
mode = get_fpu_mode()
# 如果 _old_fpu_mode 还未设置,则将当前模式赋给它
if _old_fpu_mode is None:
_old_fpu_mode = mode
# 否则,如果当前模式与旧模式不同,则记录结果到 _collect_results 字典中,并更新 _old_fpu_mode
elif mode != _old_fpu_mode:
_collect_results[item] = (_old_fpu_mode, mode)
_old_fpu_mode = mode
# 如果 HAVE_SCPDT 可用,则定义一个上下文管理器 warnings_errors_and_rng
if HAVE_SCPDT:
@contextmanager
def warnings_errors_and_rng(test=None):
"""Filter out the wall of DeprecationWarnings.
"""
# 定义需要忽略的 DeprecationWarning 的消息列表
msgs = ["The numpy.linalg.linalg",
"The numpy.fft.helper",
"dep_util",
"pkg_resources",
"numpy.core.umath",
"msvccompiler",
"Deprecated call",
"numpy.core",
"`np.compat`",
"Importing from numpy.matlib",
"This function is deprecated.", # random_integers
"Data type alias 'a'", # numpy.rec.fromfile
"Arrays of 2-dimensional vectors", # matlib.cross
"`in1d` is deprecated", ]
msg = "|".join(msgs)
# 定义需要忽略的 RuntimeWarning 的消息列表
msgs_r = [
"invalid value encountered",
"divide by zero encountered"
]
msg_r = "|".join(msgs_r)
# 忽略特定类型的警告消息
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore', category=DeprecationWarning, message=msg
)
warnings.filterwarnings(
'ignore', category=RuntimeWarning, message=msg_r
)
yield
# 将定义好的上下文管理器应用于用户配置的上下文管理器
dt_config.user_context_mgr = warnings_errors_and_rng
# 为 doctests 添加特定于 numpy 的标记以处理未初始化情况
dt_config.rndm_markers.add('#uninitialized')
dt_config.rndm_markers.add('# uninitialized')
# 导入 doctest 模块,用于查找和检查此上下文管理器下的文档测试
import doctest
# 设置 doctest 的选项标志,用于规范化空白和省略号处理
dt_config.optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
# 将 'StringDType' 识别为 numpy.dtypes.StringDType 的命名空间检查
dt_config.check_namespace['StringDType'] = numpy.dtypes.StringDType
# 设置临时跳过列表,避免在 doctest 中处理以下函数
dt_config.skiplist = set([
'numpy.savez', # 文件未关闭
'numpy.matlib.savez',
'numpy.__array_namespace_info__',
'numpy.matlib.__array_namespace_info__',
])
# 标记无法通过测试的教程文件为 xfail(预期失败),附加信息为空字符串
dt_config.pytest_extra_xfail = {
'how-to-verify-bug.rst': '',
'c-info.ufunc-tutorial.rst': '',
'basics.interoperability.rst': 'needs pandas', # 需要 pandas
'basics.dispatch.rst': 'errors out in /testing/overrides.py', # 在 /testing/overrides.py 中出错
'basics.subclassing.rst': '.. testcode:: admonitions not understood' # 不理解警告
}
# 设置额外的忽略列表,用于不希望进行 doctest 集合的内容(例如可选内容)
dt_config.pytest_extra_ignore = [
'numpy/distutils',
'numpy/_core/cversions.py',
'numpy/_pyinstaller',
'numpy/random/_examples',
'numpy/compat',
'numpy/f2py/_backends/_distutils.py',
]
.\numpy\numpy\core\arrayprint.py
# 定义一个特殊方法 __getattr__,用于在对象中获取指定属性的值
def __getattr__(attr_name):
# 从 numpy._core.arrayprint 模块中导入 arrayprint 函数
from numpy._core import arrayprint
# 从 ._utils 模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试从 arrayprint 模块中获取指定名称的属性值
ret = getattr(arrayprint, attr_name, None)
# 如果获取的属性值为 None,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.arrayprint' has no attribute {attr_name}")
# 调用 _raise_warning 函数,提醒获取到的属性名称和其所在模块
_raise_warning(attr_name, "arrayprint")
# 返回获取到的属性值
return ret
.\numpy\numpy\core\defchararray.py
# 定义一个特殊方法 __getattr__,用于在获取不存在的属性时进行处理
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 defchararray 对象
from numpy._core import defchararray
# 从 ._utils 模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 defchararray 对象中的属性 attr_name
ret = getattr(defchararray, attr_name, None)
# 如果获取不到该属性,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.defchararray' has no attribute {attr_name}")
# 调用 _raise_warning 函数,向用户发出警告信息
_raise_warning(attr_name, "defchararray")
# 返回获取到的属性或方法对象
return ret
.\numpy\numpy\core\einsumfunc.py
# 定义一个特殊的属性获取方法,用于获取指定名称的属性值
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 einsumfunc 函数或属性
from numpy._core import einsumfunc
# 从 ._utils 模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 einsumfunc 模块中指定名称的属性值
ret = getattr(einsumfunc, attr_name, None)
# 如果未找到指定属性,抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.einsumfunc' has no attribute {attr_name}")
# 调用 _raise_warning 函数,发出警告,提醒 einsumfunc 模块中的属性被访问
_raise_warning(attr_name, "einsumfunc")
# 返回获取到的属性值
return ret
.\numpy\numpy\core\fromnumeric.py
# 定义一个特殊的方法,用于动态获取属性值
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 fromnumeric 对象
from numpy._core import fromnumeric
# 从当前模块的 _utils 导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 fromnumeric 对象的属性 attr_name
ret = getattr(fromnumeric, attr_name, None)
# 如果未找到对应属性,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.fromnumeric' has no attribute {attr_name}")
# 调用 _raise_warning 函数,发出警告信息
_raise_warning(attr_name, "fromnumeric")
# 返回获取到的属性值
return ret
.\numpy\numpy\core\function_base.py
# 定义一个函数 __getattr__,用于获取指定属性名的属性值
def __getattr__(attr_name):
# 从 numpy._core 中导入 function_base 模块
from numpy._core import function_base
# 从 ._utils 模块导入 _raise_warning 函数
from ._utils import _raise_warning
# 获取 function_base 模块中名称为 attr_name 的属性值,如果不存在则返回 None
ret = getattr(function_base, attr_name, None)
# 如果未找到指定的属性值,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.function_base' has no attribute {attr_name}")
# 发出警告,说明找到的属性值来自 function_base 模块
_raise_warning(attr_name, "function_base")
# 返回找到的属性值
return ret
.\numpy\numpy\core\getlimits.py
# 定义一个特殊的属性访问方法,用于动态获取属性值
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 getlimits 函数
from numpy._core import getlimits
# 从当前模块的 _utils 导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 getlimits 模块中名为 attr_name 的属性值
ret = getattr(getlimits, attr_name, None)
# 如果获取的属性值为 None,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.getlimits' has no attribute {attr_name}")
# 调用 _raise_warning 函数,发出警告
_raise_warning(attr_name, "getlimits")
# 返回获取到的属性值
return ret
.\numpy\numpy\core\multiarray.py
# 从 numpy._core 模块中导入 multiarray 对象
from numpy._core import multiarray
# 将 "multiarray" 模块中的 "_reconstruct" 和 "scalar" 属性复制给全局命名空间,
# 以支持旧的 pickle 文件
for item in ["_reconstruct", "scalar"]:
globals()[item] = getattr(multiarray, item)
# Pybind11(在版本 <= 2.11.1 中)从 multiarray 子模块导入 _ARRAY_API 作为 NumPy 初始化的一部分,
# 因此它必须可以无警告导入。
_ARRAY_API = multiarray._ARRAY_API
# 定义 __getattr__ 函数,用于动态获取属性
def __getattr__(attr_name):
# 重新导入 multiarray 对象,用于获取特定属性
from numpy._core import multiarray
# 导入 _raise_warning 函数,用于抛出警告
from ._utils import _raise_warning
# 尝试从 multiarray 中获取指定属性
ret = getattr(multiarray, attr_name, None)
# 如果获取失败,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.multiarray' has no attribute {attr_name}")
# 发出警告,说明属性来自 multiarray
_raise_warning(attr_name, "multiarray")
return ret
# 删除已导入的 multiarray 对象的引用,清理命名空间
del multiarray
.\numpy\numpy\core\numeric.py
# 定义一个特殊方法 __getattr__,用于在当前作用域中动态获取属性
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 numeric 对象
from numpy._core import numeric
# 从当前模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 定义一个特殊的对象 sentinel,用于标记属性是否存在
sentinel = object()
# 尝试从 numeric 对象中获取指定名称的属性,如果属性不存在,则使用 sentinel 标记
ret = getattr(numeric, attr_name, sentinel)
# 如果 ret 是 sentinel,则表示属性不存在,抛出 AttributeError 异常
if ret is sentinel:
raise AttributeError(
f"module 'numpy.core.numeric' has no attribute {attr_name}")
# 调用 _raise_warning 函数,向用户发出警告,说明属性从 numeric 模块中获取
_raise_warning(attr_name, "numeric")
# 返回获取到的属性对象
return ret
.\numpy\numpy\core\numerictypes.py
# 定义一个特殊方法 __getattr__,用于在属性未找到时执行
def __getattr__(attr_name):
# 从 numpy._core 模块导入 numerictypes 对象
from numpy._core import numerictypes
# 从 ._utils 模块导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 numerictypes 模块中的属性 attr_name
ret = getattr(numerictypes, attr_name, None)
# 如果未找到该属性,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.numerictypes' has no attribute {attr_name}")
# 调用 _raise_warning 函数,传递 attr_name 和 "numerictypes" 作为参数
_raise_warning(attr_name, "numerictypes")
# 返回获取到的属性 ret
return ret
.\numpy\numpy\core\overrides.py
# 定义一个特殊方法 __getattr__,用于动态获取属性的值
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 overrides 函数
from numpy._core import overrides
# 从当前模块的 _utils 模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 获取 overrides 模块中名为 attr_name 的属性
ret = getattr(overrides, attr_name, None)
# 如果未找到对应属性,抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.overrides' has no attribute {attr_name}")
# 调用 _raise_warning 函数,生成警告信息
_raise_warning(attr_name, "overrides")
# 返回获取到的属性值
return ret
.\numpy\numpy\core\records.py
# 定义一个特殊的方法,用于动态获取对象的属性
def __getattr__(attr_name):
# 从 numpy._core.records 模块中导入 records 对象
from numpy._core import records
# 从当前模块的 ._utils 模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 records 对象中名为 attr_name 的属性
ret = getattr(records, attr_name, None)
# 如果未找到指定属性,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.records' has no attribute {attr_name}")
# 调用 _raise_warning 函数,发出警告,说明属性来自 records 模块
_raise_warning(attr_name, "records")
# 返回获取到的属性对象
return ret
.\numpy\numpy\core\shape_base.py
# 定义一个特殊方法 __getattr__,用于在访问对象的属性失败时自定义处理
def __getattr__(attr_name):
# 从 numpy._core 模块导入 shape_base 对象
from numpy._core import shape_base
# 从当前模块的 _utils 模块导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 shape_base 对象中的属性 attr_name
ret = getattr(shape_base, attr_name, None)
# 如果未找到对应属性,抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.shape_base' has no attribute {attr_name}")
# 发出警告,说明属性 attr_name 未直接在 shape_base 中找到
_raise_warning(attr_name, "shape_base")
# 返回找到的属性对象
return ret
.\numpy\numpy\core\umath.py
# 定义一个特殊方法 __getattr__,用于在对象中动态获取属性
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 umath 对象
from numpy._core import umath
# 从当前模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 umath 对象中名为 attr_name 的属性,如果不存在则返回 None
ret = getattr(umath, attr_name, None)
# 如果未找到指定的属性,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core.umath' has no attribute {attr_name}")
# 调用 _raise_warning 函数,警告 umath 模块中已获取属性
_raise_warning(attr_name, "umath")
# 返回获取到的属性
return ret
.\numpy\numpy\core\_dtype.py
# 定义一个特殊方法 __getattr__,用于在对象没有指定属性时进行处理
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 _dtype 对象
from numpy._core import _dtype
# 从 ._utils 模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 _dtype 对象的属性 attr_name
ret = getattr(_dtype, attr_name, None)
# 如果未找到指定属性,则引发 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core._dtype' has no attribute {attr_name}")
# 调用 _raise_warning 函数,向用户发出警告,说明属性被访问
_raise_warning(attr_name, "_dtype")
# 返回获取到的属性值
return ret
.\numpy\numpy\core\_dtype_ctypes.py
# 定义一个特殊的函数 __getattr__,用于在对象中获取指定属性的方法
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 _dtype_ctypes 对象
from numpy._core import _dtype_ctypes
# 从 ._utils 模块中导入 _raise_warning 函数
from ._utils import _raise_warning
# 尝试获取 _dtype_ctypes 对象中名为 attr_name 的属性
ret = getattr(_dtype_ctypes, attr_name, None)
# 如果未找到指定属性,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core._dtype_ctypes' has no attribute {attr_name}")
# 调用 _raise_warning 函数,提醒用户该属性来自 _dtype_ctypes 对象
_raise_warning(attr_name, "_dtype_ctypes")
# 返回获取到的属性
return ret
.\numpy\numpy\core\_internal.py
# 从 numpy._core._internal 模块导入 _reconstruct 函数
# 该函数用于从 pickle 数据中重建 ndarray 数组对象
# 注意:numpy.core._internal._reconstruct 函数名称在 NumPy 1.0 之前的版本中用于 ndarray 的 pickle 数据,
# 因此不能在此处删除名称,否则会破坏向后兼容性。
def _reconstruct(subtype, shape, dtype):
# 导入 ndarray 类型并使用其 __new__ 方法创建新的数组对象
from numpy import ndarray
return ndarray.__new__(subtype, shape, dtype)
# Pybind11(版本 <= 2.11.1)从 _internal 子模块导入 _dtype_from_pep3118 函数,
# 因此必须能够无警告地导入它。
_dtype_from_pep3118 = _internal._dtype_from_pep3118
# 定义一个 __getattr__ 函数,用于在运行时动态获取属性
def __getattr__(attr_name):
# 导入 numpy._core._internal 模块
from numpy._core import _internal
# 导入自定义工具函数 _raise_warning
from ._utils import _raise_warning
# 尝试从 _internal 模块获取指定名称的属性
ret = getattr(_internal, attr_name, None)
# 如果未找到指定名称的属性,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
f"module 'numpy.core._internal' has no attribute {attr_name}")
# 发出警告,说明此属性来自 _internal 模块
_raise_warning(attr_name, "_internal")
return ret
.\numpy\numpy\core\_multiarray_umath.py
# 从 numpy._core 模块中导入 _multiarray_umath 对象
from numpy._core import _multiarray_umath
# 从 numpy 模块中导入 ufunc 类型
from numpy import ufunc
# 遍历 _multiarray_umath 对象的所有属性名称
for item in _multiarray_umath.__dir__():
# 对于每个属性名称,获取其对应的属性对象
attr = getattr(_multiarray_umath, item)
# 如果该属性对象属于 ufunc 类型
if isinstance(attr, ufunc):
# 将该属性对象添加到全局命名空间中,属性名称和对象相同
globals()[item] = attr
# 定义一个特殊的 __getattr__ 函数
def __getattr__(attr_name):
# 从 numpy._core 模块中导入 _multiarray_umath 对象
from numpy._core import _multiarray_umath
# 从当前模块的 ._utils 子模块中导入 _raise_warning 函数
# 如果 attr_name 是 "_ARRAY_API" 或 "_UFUNC_API"
if attr_name in {"_ARRAY_API", "_UFUNC_API"}:
# 从 numpy.version 模块中导入 short_version 变量
from numpy.version import short_version
# 导入 textwrap 模块,用于格式化消息文本
import textwrap
# 导入 traceback 模块,用于生成调用堆栈信息
import traceback
# 导入 sys 模块,用于访问标准错误流
import sys
# 创建一条包含详细信息的消息字符串
msg = textwrap.dedent(f"""
A module that was compiled using NumPy 1.x cannot be run in
NumPy {short_version} as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.
If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.
""")
# 创建一个包含调用堆栈的消息字符串
tb_msg = "Traceback (most recent call last):"
for line in traceback.format_stack()[:-1]:
if "frozen importlib" in line:
continue
tb_msg += line
# 将消息和调用堆栈信息写入标准错误流
sys.stderr.write(msg + tb_msg)
# 抛出 ImportError 异常,包含详细消息
raise ImportError(msg)
# 尝试从 _multiarray_umath 对象中获取指定名称的属性对象
ret = getattr(_multiarray_umath, attr_name, None)
# 如果获取的属性对象为 None,则抛出 AttributeError 异常
if ret is None:
raise AttributeError(
"module 'numpy.core._multiarray_umath' has no attribute "
f"{attr_name}")
# 调用 _raise_warning 函数,向用户发出警告
_raise_warning(attr_name, "_multiarray_umath")
# 返回获取到的属性对象
return ret
# 从全局命名空间中删除 _multiarray_umath 和 ufunc 变量
del _multiarray_umath, ufunc
.\numpy\numpy\core\_utils.py
# 导入警告模块,用于生成警告信息
import warnings
# 定义一个函数,用于发出警告信息
def _raise_warning(attr: str, submodule: str = None) -> None:
# 定义新旧模块的名称
new_module = "numpy._core"
old_module = "numpy.core"
# 如果有子模块,添加到模块名称中
if submodule is not None:
new_module = f"{new_module}.{submodule}"
old_module = f"{old_module}.{submodule}"
# 发出警告,说明旧模块已弃用并重命名
warnings.warn(
f"{old_module} is deprecated and has been renamed to {new_module}. "
"The numpy._core namespace contains private NumPy internals and its "
"use is discouraged, as NumPy internals can change without warning in "
"any release. In practice, most real-world usage of numpy.core is to "
"access functionality in the public NumPy API. If that is the case, "
"use the public NumPy API. If not, you are using NumPy internals. "
"If you would still like to access an internal attribute, "
f"use {new_module}.{attr}.",
DeprecationWarning,
stacklevel=3 # 设置警告的堆栈级别
)
.\numpy\numpy\core\__init__.py
"""
The `numpy.core` submodule exists solely for backward compatibility
purposes. The original `core` was renamed to `_core` and made private.
`numpy.core` will be removed in the future.
"""
# 从 `numpy` 包中导入 `_core` 子模块,用于向后兼容性目的
from numpy import _core
# 从当前包的 `_utils` 模块中导入 `_raise_warning` 函数
from ._utils import _raise_warning
# We used to use `np.core._ufunc_reconstruct` to unpickle.
# This is unnecessary, but old pickles saved before 1.20 will be using it,
# and there is no reason to break loading them.
# 定义 `_ufunc_reconstruct` 函数,用于反序列化时重建 ufunc 对象
def _ufunc_reconstruct(module, name):
# 导入指定模块 `module` 并返回其中指定的 `name` 对象
# `fromlist` 参数确保当模块名嵌套时,`mod` 指向最内层模块而非父包
mod = __import__(module, fromlist=[name])
return getattr(mod, name)
# force lazy-loading of submodules to ensure a warning is printed
# 定义 `__all__` 列表,包含公开的子模块名称,用于 `from package import *` 语法
__all__ = ["arrayprint", "defchararray", "_dtype_ctypes", "_dtype",
"einsumfunc", "fromnumeric", "function_base", "getlimits",
"_internal", "multiarray", "_multiarray_umath", "numeric",
"numerictypes", "overrides", "records", "shape_base", "umath"]
# 定义 `__getattr__` 函数,用于动态获取 `numpy.core` 中的属性
def __getattr__(attr_name):
# 从 `numpy._core` 中获取指定属性 `attr_name`
attr = getattr(_core, attr_name)
# 调用 `_raise_warning` 函数,对获取的属性进行警告处理
_raise_warning(attr_name)
# 返回获取的属性
return attr
.\numpy\numpy\core\__init__.pyi
# 导入所需的模块:re 用于正则表达式操作,os 用于文件路径操作
import re
import os
# 定义函数 find_files,接收文件路径和正则表达式作为参数
def find_files(dir, regex):
# 初始化一个空列表,用于存储符合条件的文件路径
files = []
# 遍历指定目录及其子目录下的所有文件和文件夹
for root, dirs, filenames in os.walk(dir):
# 对每个文件名进行正则匹配
for filename in filenames:
# 如果文件名符合正则表达式条件
if re.match(regex, filename):
# 构建文件的完整路径并添加到列表中
files.append(os.path.join(root, filename))
# 返回符合条件的文件路径列表
return files
.\numpy\numpy\ctypeslib.py
"""
============================
``ctypes`` Utility Functions
============================
See Also
--------
load_library : Load a C library.
ndpointer : Array restype/argtype with verification.
as_ctypes : Create a ctypes array from an ndarray.
as_array : Create an ndarray from a ctypes array.
References
----------
.. [1] "SciPy Cookbook: ctypes", https://scipy-cookbook.readthedocs.io/items/Ctypes.html
Examples
--------
Load the C library:
>>> _lib = np.ctypeslib.load_library('libmystuff', '.') #doctest: +SKIP
Our result type, an ndarray that must be of type double, be 1-dimensional
and is C-contiguous in memory:
>>> array_1d_double = np.ctypeslib.ndpointer(
... dtype=np.double,
... ndim=1, flags='CONTIGUOUS') #doctest: +SKIP
Our C-function typically takes an array and updates its values
in-place. For example::
void foo_func(double* x, int length)
{
int i;
for (i = 0; i < length; i++) {
x[i] = i*i;
}
}
We wrap it using:
>>> _lib.foo_func.restype = None #doctest: +SKIP
>>> _lib.foo_func.argtypes = [array_1d_double, c_int] #doctest: +SKIP
Then, we're ready to call ``foo_func``:
>>> out = np.empty(15, dtype=np.double)
>>> _lib.foo_func(out, len(out)) #doctest: +SKIP
"""
__all__ = ['load_library', 'ndpointer', 'c_intp', 'as_ctypes', 'as_array',
'as_ctypes_type']
import os # 导入操作系统接口模块
from numpy import ( # 从 numpy 库导入以下模块:
integer, ndarray, dtype as _dtype, asarray, frombuffer
)
from numpy._core.multiarray import _flagdict, flagsobj # 导入 numpy 的内部多维数组相关模块
try:
import ctypes # 尝试导入 ctypes 库
except ImportError:
ctypes = None
if ctypes is None: # 如果 ctypes 库不可用,定义一个 _dummy 函数来抛出 ImportError
def _dummy(*args, **kwds):
"""
Dummy object that raises an ImportError if ctypes is not available.
Raises
------
ImportError
If ctypes is not available.
"""
raise ImportError("ctypes is not available.")
load_library = _dummy # 定义 load_library, as_ctypes, as_array 为 _dummy 函数
as_ctypes = _dummy
as_array = _dummy
from numpy import intp as c_intp # 从 numpy 导入 intp 并命名为 c_intp
_ndptr_base = object # _ndptr_base 设为 Python 对象
else:
import numpy._core._internal as nic # 导入 numpy 内部的 _internal 模块并命名为 nic
c_intp = nic._getintp_ctype() # 获取 ctypes 中的 c_intp 类型
del nic # 删除 nic 引用,释放内存
_ndptr_base = ctypes.c_void_p # 设置 _ndptr_base 为 ctypes 的 c_void_p 类型
# Adapted from Albert Strasheim
def load_library(libname, loader_path):
"""
It is possible to load a library using
>>> lib = ctypes.cdll[<full_path_name>] # doctest: +SKIP
But there are cross-platform considerations, such as library file extensions,
plus the fact Windows will just load the first library it finds with that name.
NumPy supplies the load_library function as a convenience.
.. versionchanged:: 1.20.0
Allow libname and loader_path to take any
:term:`python:path-like object`.
Parameters
----------
libname : path-like
Name of the library, which can have 'lib' as a prefix,
but without an extension.
loader_path : path-like
Where the library can be found.
Returns
-------
ctypes.cdll[libpath] : library object
A ctypes library object
Raises
------
OSError
If there is no library with the expected extension, or the
library is defective and cannot be loaded.
"""
# Convert path-like objects into strings
libname = os.fsdecode(libname) # 解码并获取库名称的字符串表示
loader_path = os.fsdecode(loader_path) # 解码并获取加载路径的字符串表示
ext = os.path.splitext(libname)[1] # 获取库名称的文件扩展名
if not ext:
import sys
import sysconfig
# 尝试使用平台特定的库文件名加载库,否则默认为libname.[so|dll|dylib]。
# 有时这些文件在非Linux平台上会构建错误。
base_ext = ".so"
if sys.platform.startswith("darwin"):
base_ext = ".dylib"
elif sys.platform.startswith("win"):
base_ext = ".dll"
libname_ext = [libname + base_ext]
so_ext = sysconfig.get_config_var("EXT_SUFFIX")
if not so_ext == base_ext:
libname_ext.insert(0, libname + so_ext)
else:
libname_ext = [libname]
loader_path = os.path.abspath(loader_path) # 获取加载路径的绝对路径
if not os.path.isdir(loader_path):
libdir = os.path.dirname(loader_path) # 获取加载路径的父目录
else:
libdir = loader_path
for ln in libname_ext:
libpath = os.path.join(libdir, ln) # 组合库文件的完整路径
if os.path.exists(libpath): # 检查库文件是否存在
try:
return ctypes.cdll[libpath] # 尝试加载库文件并返回 ctypes 库对象
except OSError:
## defective lib file
raise # 抛出异常,说明库文件有问题
## if no successful return in the libname_ext loop:
raise OSError("no file with expected extension") # 如果在 libname_ext 循环中没有成功返回,则抛出异常
def _num_fromflags(flaglist):
# 初始化一个计数器
num = 0
# 遍历传入的标志列表,将每个标志对应的值累加到计数器中
for val in flaglist:
num += _flagdict[val]
# 返回累加后的结果作为标志的数字表示
return num
_flagnames = ['C_CONTIGUOUS', 'F_CONTIGUOUS', 'ALIGNED', 'WRITEABLE',
'OWNDATA', 'WRITEBACKIFCOPY']
def _flags_fromnum(num):
# 初始化一个空列表用来存储结果
res = []
# 遍历已定义的所有标志名称
for key in _flagnames:
# 获取当前标志对应的数值
value = _flagdict[key]
# 检查当前标志是否在给定的数字表示中
if (num & value):
# 如果在其中,则将该标志名称添加到结果列表中
res.append(key)
# 返回所有匹配的标志名称列表
return res
class _ndptr(_ndptr_base):
@classmethod
def from_param(cls, obj):
# 检查传入的对象是否为 ndarray 类型
if not isinstance(obj, ndarray):
raise TypeError("argument must be an ndarray")
# 如果定义了特定的数据类型,检查传入数组是否符合要求
if cls._dtype_ is not None \
and obj.dtype != cls._dtype_:
raise TypeError("array must have data type %s" % cls._dtype_)
# 如果定义了特定的维度数,检查传入数组是否符合要求
if cls._ndim_ is not None \
and obj.ndim != cls._ndim_:
raise TypeError("array must have %d dimension(s)" % cls._ndim_)
# 如果定义了特定的形状,检查传入数组是否符合要求
if cls._shape_ is not None \
and obj.shape != cls._shape_:
raise TypeError("array must have shape %s" % str(cls._shape_))
# 如果定义了特定的标志,检查传入数组是否符合要求
if cls._flags_ is not None \
and ((obj.flags.num & cls._flags_) != cls._flags_):
raise TypeError("array must have flags %s" %
_flags_fromnum(cls._flags_))
# 返回传入数组的 ctypes 对象
return obj.ctypes
class _concrete_ndptr(_ndptr):
"""
Like _ndptr, but with `_shape_` and `_dtype_` specified.
Notably, this means the pointer has enough information to reconstruct
the array, which is not generally true.
"""
def _check_retval_(self):
"""
This method is called when this class is used as the .restype
attribute for a shared-library function, to automatically wrap the
pointer into an array.
"""
# 返回指针指向的数据作为 ndarray 对象
return self.contents
@property
def contents(self):
"""
Get an ndarray viewing the data pointed to by this pointer.
This mirrors the `contents` attribute of a normal ctypes pointer
"""
# 构建完整的数据类型描述
full_dtype = _dtype((self._dtype_, self._shape_))
# 根据完整的数据类型描述创建对应的 ctypes 类型
full_ctype = ctypes.c_char * full_dtype.itemsize
# 将当前指针对象转换为指向完整 ctypes 类型的指针,并获取其内容
buffer = ctypes.cast(self, ctypes.POINTER(full_ctype)).contents
# 将 ctypes 缓冲区转换为 ndarray,并去掉多余的维度
return frombuffer(buffer, dtype=full_dtype).squeeze(axis=0)
# Factory for an array-checking class with from_param defined for
# use with ctypes argtypes mechanism
_pointer_type_cache = {}
def ndpointer(dtype=None, ndim=None, shape=None, flags=None):
"""
Array-checking restype/argtypes.
An ndpointer instance is used to describe an ndarray in restypes
and argtypes specifications. This approach is more flexible than
using, for example, ``POINTER(c_double)``, since several restrictions
can be specified, which are verified upon calling the ctypes function.
These include data type, number of dimensions, shape and flags. If a
given array does not satisfy the specified restrictions,
a ``TypeError`` is raised.
Parameters
----------
"""
# 此函数主要用于创建一个描述 ndarray 的类型,检查其数据类型、维度、形状和标志
pass
# data-type 数据类型,可选参数
dtype : data-type, optional
# int 整数,可选参数
ndim : int, optional
# tuple of ints 整数元组,可选参数
shape : tuple of ints, optional
# str or tuple of str 字符串或字符串元组,数组标志;可以是以下一项或多项:
# C_CONTIGUOUS / C / CONTIGUOUS
# F_CONTIGUOUS / F / FORTRAN
# OWNDATA / O
# WRITEABLE / W
# ALIGNED / A
# WRITEBACKIFCOPY / X
# 返回
-------
# ndpointer 类型对象
klass : ndpointer type object
# 类型对象,是一个包含 dtype、ndim、shape 和 flags 信息的 `_ndtpr` 实例。
# 异常
------
# TypeError
# 如果给定的数组不满足指定的限制条件。
# 示例
--------
# 将 clib.somefunc 的 argtypes 设置为 [np.ctypeslib.ndpointer(dtype=np.float64,
# ndim=1,
# flags='C_CONTIGUOUS')]
... #doctest: +SKIP
# 调用 clib.somefunc,传入 np.array([1, 2, 3], dtype=np.float64) 作为参数
... #doctest: +SKIP
"""
# 将 dtype 标准化为 Optional[dtype]
if dtype is not None:
dtype = _dtype(dtype)
# 将 flags 标准化为 Optional[int]
num = None
if flags is not None:
if isinstance(flags, str):
flags = flags.split(',')
elif isinstance(flags, (int, integer)):
num = flags
flags = _flags_fromnum(num)
elif isinstance(flags, flagsobj):
num = flags.num
flags = _flags_fromnum(num)
if num is None:
try:
flags = [x.strip().upper() for x in flags]
except Exception as e:
raise TypeError("invalid flags specification") from e
num = _num_fromflags(flags)
# 将 shape 标准化为 Optional[tuple]
if shape is not None:
try:
shape = tuple(shape)
except TypeError:
# 单个整数 -> 转为 1 元组
shape = (shape,)
# 缓存键,包含 dtype、ndim、shape 和 num
cache_key = (dtype, ndim, shape, num)
try:
# 尝试从缓存中获取 _pointer_type_cache 中的值
return _pointer_type_cache[cache_key]
except KeyError:
pass
# 为新类型生成一个名称
if dtype is None:
name = 'any'
elif dtype.names is not None:
name = str(id(dtype))
else:
name = dtype.str
if ndim is not None:
name += "_%dd" % ndim
if shape is not None:
name += "_"+"x".join(str(x) for x in shape)
if flags is not None:
name += "_"+"_".join(flags)
# 如果 dtype 和 shape 都不为 None,则基于 _concrete_ndptr
# 否则基于 _ndptr
if dtype is not None and shape is not None:
base = _concrete_ndptr
else:
base = _ndptr
# 创建一个新类型 klass,类型名为 'ndpointer_%s' % name
klass = type("ndpointer_%s"%name, (base,),
{"_dtype_": dtype,
"_shape_" : shape,
"_ndim_" : ndim,
"_flags_" : num})
# 将 klass 存储到 _pointer_type_cache 中,使用 cache_key 作为键
_pointer_type_cache[cache_key] = klass
return klass
if ctypes is not None:
# 定义函数 _ctype_ndarray,用于创建给定元素类型和形状的 ndarray
def _ctype_ndarray(element_type, shape):
""" Create an ndarray of the given element type and shape """
# 反向遍历形状,逐步构建元素类型
for dim in shape[::-1]:
element_type = dim * element_type
# 防止类型名称包含 np.ctypeslib
element_type.__module__ = None
return element_type
# 定义函数 _get_scalar_type_map,返回将本机字节序标量 dtype 映射到 ctypes 类型的字典
def _get_scalar_type_map():
"""
Return a dictionary mapping native endian scalar dtype to ctypes types
"""
ct = ctypes
# 定义简单的 ctypes 类型列表
simple_types = [
ct.c_byte, ct.c_short, ct.c_int, ct.c_long, ct.c_longlong,
ct.c_ubyte, ct.c_ushort, ct.c_uint, ct.c_ulong, ct.c_ulonglong,
ct.c_float, ct.c_double,
ct.c_bool,
]
# 返回字典,映射 dtype 到对应的 ctypes 类型
return {_dtype(ctype): ctype for ctype in simple_types}
# 获取本机字节序标量 dtype 到 ctypes 类型的映射
_scalar_type_map = _get_scalar_type_map()
# 定义函数 _ctype_from_dtype_scalar,根据 dtype 返回对应的 ctypes 类型
def _ctype_from_dtype_scalar(dtype):
# 确保将 `=` 转换为本机字节序的 <, >, 或 |
dtype_with_endian = dtype.newbyteorder('S').newbyteorder('S')
dtype_native = dtype.newbyteorder('=')
try:
# 根据本机字节序的 dtype 获取对应的 ctypes 类型
ctype = _scalar_type_map[dtype_native]
except KeyError as e:
# 抛出异常,表示无法转换该 dtype 到 ctypes 类型
raise NotImplementedError(
"Converting {!r} to a ctypes type".format(dtype)
) from None
# 根据 dtype 的字节序调整 ctypes 类型
if dtype_with_endian.byteorder == '>':
ctype = ctype.__ctype_be__
elif dtype_with_endian.byteorder == '<':
ctype = ctype.__ctype_le__
return ctype
# 定义函数 _ctype_from_dtype_subarray,根据 dtype 的子数组返回对应的 ctypes 类型
def _ctype_from_dtype_subarray(dtype):
# 获取元素 dtype 和形状
element_dtype, shape = dtype.subdtype
# 根据元素 dtype 获取对应的 ctypes 类型
ctype = _ctype_from_dtype(element_dtype)
# 使用 _ctype_ndarray 创建 ndarray 类型,并返回
return _ctype_ndarray(ctype, shape)
# 根据结构化数据类型(dtype)创建对应的 ctypes 类型,支持嵌套结构和数组
def _ctype_from_dtype_structured(dtype):
# 提取每个字段的偏移量信息
field_data = []
for name in dtype.names:
# 获取字段的数据类型和偏移量
field_dtype, offset = dtype.fields[name][:2]
# 将字段信息存入列表
field_data.append((offset, name, _ctype_from_dtype(field_dtype)))
# ctypes 不关心字段的顺序,按偏移量排序字段信息
field_data = sorted(field_data, key=lambda f: f[0])
# 如果有多个字段且所有字段的偏移量均为 0,则为联合体(union)
if len(field_data) > 1 and all(offset == 0 for offset, name, ctype in field_data):
# 初始化联合体的大小和字段列表
size = 0
_fields_ = []
for offset, name, ctype in field_data:
_fields_.append((name, ctype))
size = max(size, ctypes.sizeof(ctype))
# 如果结构体的总大小与 dtype 中定义的大小不一致,则添加填充字段
if dtype.itemsize != size:
_fields_.append(('', ctypes.c_char * dtype.itemsize))
# 手动插入了填充字段,因此总是设置 `_pack_` 为 1
return type('union', (ctypes.Union,), dict(
_fields_=_fields_,
_pack_=1,
__module__=None,
))
else:
last_offset = 0
_fields_ = []
for offset, name, ctype in field_data:
# 计算字段之间的填充空间
padding = offset - last_offset
if padding < 0:
raise NotImplementedError("Overlapping fields")
if padding > 0:
_fields_.append(('', ctypes.c_char * padding))
_fields_.append((name, ctype))
last_offset = offset + ctypes.sizeof(ctype)
# 计算最后一个字段之后的填充空间
padding = dtype.itemsize - last_offset
if padding > 0:
_fields_.append(('', ctypes.c_char * padding))
# 手动插入了填充字段,因此总是设置 `_pack_` 为 1
return type('struct', (ctypes.Structure,), dict(
_fields_=_fields_,
_pack_=1,
__module__=None,
))
def _ctype_from_dtype(dtype):
# 如果数据类型具有字段信息,则调用 _ctype_from_dtype_structured 处理
if dtype.fields is not None:
return _ctype_from_dtype_structured(dtype)
# 如果数据类型具有子数据类型信息,则调用 _ctype_from_dtype_subarray 处理
elif dtype.subdtype is not None:
return _ctype_from_dtype_subarray(dtype)
# 否则,将数据类型视为标量,调用 _ctype_from_dtype_scalar 处理
else:
return _ctype_from_dtype_scalar(dtype)
def as_ctypes_type(dtype):
r"""
Convert a dtype into a ctypes type.
Parameters
----------
dtype : dtype
The dtype to convert
Returns
-------
ctype
A ctype scalar, union, array, or struct
Raises
------
NotImplementedError
If the conversion is not possible
Notes
-----
This function does not losslessly round-trip in either direction.
``np.dtype(as_ctypes_type(dt))`` will:
- insert padding fields
- reorder fields to be sorted by offset
- discard field titles
``as_ctypes_type(np.dtype(ctype))`` will:
- discard the class names of `ctypes.Structure`\ s and
`ctypes.Union`\ s
- convert single-element `ctypes.Union`\ s into single-element
`ctypes.Structure`\ s
- insert padding fields
"""
# 调用内部函数 _ctype_from_dtype 进行 dtype 到 ctypes 类型的转换
return _ctype_from_dtype(_dtype(dtype))
def as_array(obj, shape=None):
"""
Create a numpy array from a ctypes array or POINTER.
The numpy array shares the memory with the ctypes object.
The shape parameter must be given if converting from a ctypes POINTER.
The shape parameter is ignored if converting from a ctypes array
"""
if isinstance(obj, ctypes._Pointer):
# 如果 obj 是 ctypes._Pointer 类型,则将其转换为指定 shape 的数组
# 如果 shape 为 None,则抛出 TypeError 异常
if shape is None:
raise TypeError(
'as_array() requires a shape argument when called on a '
'pointer')
# 构造指向 obj 的指针类型 p_arr_type
p_arr_type = ctypes.POINTER(_ctype_ndarray(obj._type_, shape))
# 使用 ctypes.cast 将 obj 转换为 p_arr_type 指向的内容(数组)
obj = ctypes.cast(obj, p_arr_type).contents
# 调用 asarray 函数,返回 obj 的 numpy 数组表示
return asarray(obj)
def as_ctypes(obj):
"""Create and return a ctypes object from a numpy array. Actually
anything that exposes the __array_interface__ is accepted."""
# 获取 obj 的 __array_interface__
ai = obj.__array_interface__
# 如果数组是 strided arrays,则抛出 TypeError 异常
if ai["strides"]:
raise TypeError("strided arrays not supported")
# 如果 __array_interface__ 的版本不是 3,则抛出 TypeError 异常
if ai["version"] != 3:
raise TypeError("only __array_interface__ version 3 supported")
# 获取数组的数据地址和 readonly 属性
addr, readonly = ai["data"]
# 如果数组是只读的,则抛出 TypeError 异常
if readonly:
raise TypeError("readonly arrays unsupported")
# 根据 ai["typestr"] 调用 as_ctypes_type 函数转换为对应的 ctypes 标量类型
ctype_scalar = as_ctypes_type(ai["typestr"])
# 构造一个 ctypes 对象,类型是 _ctype_ndarray(ctype_scalar, ai["shape"])
result_type = _ctype_ndarray(ctype_scalar, ai["shape"])
# 使用 from_address 方法从地址 addr 创建 result_type 类型的对象 result
result = result_type.from_address(addr)
# 将 obj 保存在 result 的 __keep 属性中
result.__keep = obj
return result
.\numpy\numpy\ctypeslib.pyi
# NOTE: Numpy's mypy plugin is used for importing the correct
# platform-specific `ctypes._SimpleCData[int]` sub-type
# 导入正确的平台特定的 `ctypes._SimpleCData[int]` 子类型,使用了 Numpy 的 mypy 插件
from ctypes import c_int64 as _c_intp
import os
import ctypes
from collections.abc import Iterable, Sequence
from typing import (
Literal as L,
Any,
TypeVar,
Generic,
overload,
ClassVar,
)
import numpy as np
from numpy import (
ndarray,
dtype,
generic,
byte,
short,
intc,
long,
longlong,
intp,
ubyte,
ushort,
uintc,
ulong,
ulonglong,
uintp,
single,
double,
longdouble,
void,
)
from numpy._core._internal import _ctypes
from numpy._core.multiarray import flagsobj
from numpy._typing import (
# Arrays
NDArray,
_ArrayLike,
# Shapes
_ShapeLike,
# DTypes
DTypeLike,
_DTypeLike,
_VoidDTypeLike,
_BoolCodes,
_UByteCodes,
_UShortCodes,
_UIntCCodes,
_ULongCodes,
_ULongLongCodes,
_ByteCodes,
_ShortCodes,
_IntCCodes,
_LongCodes,
_LongLongCodes,
_SingleCodes,
_DoubleCodes,
_LongDoubleCodes,
)
# TODO: Add a proper `_Shape` bound once we've got variadic typevars
# TODO: 添加适当的 `_Shape` 约束一旦我们有了变长类型变量(PEP 646)
_DType = TypeVar("_DType", bound=dtype[Any])
_DTypeOptional = TypeVar("_DTypeOptional", bound=None | dtype[Any])
_SCT = TypeVar("_SCT", bound=generic)
_FlagsKind = L[
'C_CONTIGUOUS', 'CONTIGUOUS', 'C',
'F_CONTIGUOUS', 'FORTRAN', 'F',
'ALIGNED', 'A',
'WRITEABLE', 'W',
'OWNDATA', 'O',
'WRITEBACKIFCOPY', 'X',
]
# TODO: Add a shape typevar once we have variadic typevars (PEP 646)
# TODO: 一旦有了变长类型变量,添加一个形状类型变量(PEP 646)
class _ndptr(ctypes.c_void_p, Generic[_DTypeOptional]):
# In practice these 4 classvars are defined in the dynamic class
# returned by `ndpointer`
# 实际上这四个类变量在由 `ndpointer` 返回的动态类中定义
_dtype_: ClassVar[_DTypeOptional]
_shape_: ClassVar[None]
_ndim_: ClassVar[None | int]
_flags_: ClassVar[None | list[_FlagsKind]]
@overload
@classmethod
def from_param(cls: type[_ndptr[None]], obj: NDArray[Any]) -> _ctypes[Any]: ...
@overload
@classmethod
def from_param(cls: type[_ndptr[_DType]], obj: ndarray[Any, _DType]) -> _ctypes[Any]: ...
class _concrete_ndptr(_ndptr[_DType]):
_dtype_: ClassVar[_DType]
_shape_: ClassVar[tuple[int, ...]]
@property
def contents(self) -> ndarray[Any, _DType]: ...
# 属性方法,返回 ndarray,其元素类型为 `_DType`
def load_library(
libname: str | bytes | os.PathLike[str] | os.PathLike[bytes],
loader_path: str | bytes | os.PathLike[str] | os.PathLike[bytes],
) -> ctypes.CDLL:
# 加载动态链接库,返回 ctypes.CDLL 对象
pass
__all__: list[str]
c_intp = _c_intp
@overload
def ndpointer(
dtype: None = ...,
ndim: int = ...,
shape: None | _ShapeLike = ...,
flags: None | _FlagsKind | Iterable[_FlagsKind] | int | flagsobj = ...,
) -> type[_ndptr[None]]: ...
@overload
def ndpointer(
dtype: _DTypeLike[_SCT],
ndim: int = ...,
*,
shape: _ShapeLike,
flags: None | _FlagsKind | Iterable[_FlagsKind] | int | flagsobj = ...,
) -> type[_concrete_ndptr[dtype[_SCT]]]: ...
@overload
def ndpointer(
dtype: DTypeLike,
ndim: int = ...,
*,
shape: _ShapeLike = ...,
flags: None | _FlagsKind | Iterable[_FlagsKind] | int | flagsobj = ...,
) -> type[_ndptr[dtype]]: ...
# 根据指定的参数生成 ndarray 指针类型,支持多种重载形式
pass
ndim: int = ..., # 定义一个类型为整数的变量 ndim,并初始化为占位符 ...
*, # 分隔位置参数和关键字参数的标记
shape: _ShapeLike, # 定义一个形状变量 shape,其类型为 _ShapeLike
flags: None | _FlagsKind | Iterable[_FlagsKind] | int | flagsobj = ..., # 定义一个 flags 变量,其类型可以是 None、_FlagsKind 类型、_FlagsKind 的可迭代对象、整数或 flagsobj 类型,并初始化为占位符 ...
# 定义一个函数签名,用于创建特定类型的指针对象
def ndpointer(
dtype: _DTypeLike[_SCT], # 数据类型参数,可以是具体类型或类型的别名
ndim: int = ..., # 数组的维度,默认为省略值,表示维度不固定
shape: None = ..., # 数组的形状,默认为None,表示形状不固定
flags: None | _FlagsKind | Iterable[_FlagsKind] | int | flagsobj = ..., # 标志参数,可以是None、单个标志、标志集合或整数
) -> type[_ndptr[dtype[_SCT]]]: # 返回值类型为特定数据类型的指针类型
# 函数重载,支持更多的数据类型作为输入
def ndpointer(
dtype: DTypeLike, # 数据类型参数,可以是具体类型或类型的别名
ndim: int = ..., # 数组的维度,默认为省略值,表示维度不固定
shape: None = ..., # 数组的形状,默认为None,表示形状不固定
flags: None | _FlagsKind | Iterable[_FlagsKind] | int | flagsobj = ..., # 标志参数,可以是None、单个标志、标志集合或整数
) -> type[_ndptr[dtype[Any]]]: # 返回值类型为任意数据类型的指针类型
# 函数重载,将特定的 NumPy 数据类型转换为对应的 ctypes 类型
def as_ctypes_type(dtype: _BoolCodes | _DTypeLike[np.bool] | type[ctypes.c_bool]) -> type[ctypes.c_bool]: # 返回值类型为 ctypes.c_bool 类型
def as_ctypes_type(dtype: _ByteCodes | _DTypeLike[byte] | type[ctypes.c_byte]) -> type[ctypes.c_byte]: # 返回值类型为 ctypes.c_byte 类型
def as_ctypes_type(dtype: _ShortCodes | _DTypeLike[short] | type[ctypes.c_short]) -> type[ctypes.c_short]: # 返回值类型为 ctypes.c_short 类型
def as_ctypes_type(dtype: _IntCCodes | _DTypeLike[intc] | type[ctypes.c_int]) -> type[ctypes.c_int]: # 返回值类型为 ctypes.c_int 类型
def as_ctypes_type(dtype: _LongCodes | _DTypeLike[long] | type[ctypes.c_long]) -> type[ctypes.c_long]: # 返回值类型为 ctypes.c_long 类型
def as_ctypes_type(dtype: type[int]) -> type[c_intp]: # 返回值类型为 c_intp 类型
def as_ctypes_type(dtype: _LongLongCodes | _DTypeLike[longlong] | type[ctypes.c_longlong]) -> type[ctypes.c_longlong]: # 返回值类型为 ctypes.c_longlong 类型
def as_ctypes_type(dtype: _UByteCodes | _DTypeLike[ubyte] | type[ctypes.c_ubyte]) -> type[ctypes.c_ubyte]: # 返回值类型为 ctypes.c_ubyte 类型
def as_ctypes_type(dtype: _UShortCodes | _DTypeLike[ushort] | type[ctypes.c_ushort]) -> type[ctypes.c_ushort]: # 返回值类型为 ctypes.c_ushort 类型
def as_ctypes_type(dtype: _UIntCCodes | _DTypeLike[uintc] | type[ctypes.c_uint]) -> type[ctypes.c_uint]: # 返回值类型为 ctypes.c_uint 类型
def as_ctypes_type(dtype: _ULongCodes | _DTypeLike[ulong] | type[ctypes.c_ulong]) -> type[ctypes.c_ulong]: # 返回值类型为 ctypes.c_ulong 类型
def as_ctypes_type(dtype: _ULongLongCodes | _DTypeLike[ulonglong] | type[ctypes.c_ulonglong]) -> type[ctypes.c_ulonglong]: # 返回值类型为 ctypes.c_ulonglong 类型
def as_ctypes_type(dtype: _SingleCodes | _DTypeLike[single] | type[ctypes.c_float]) -> type[ctypes.c_float]: # 返回值类型为 ctypes.c_float 类型
def as_ctypes_type(dtype: _DoubleCodes | _DTypeLike[double] | type[float | ctypes.c_double]) -> type[ctypes.c_double]: # 返回值类型为 ctypes.c_double 类型
def as_ctypes_type(dtype: _LongDoubleCodes | _DTypeLike[longdouble] | type[ctypes.c_longdouble]) -> type[ctypes.c_longdouble]: # 返回值类型为 ctypes.c_longdouble 类型
def as_ctypes_type(dtype: _VoidDTypeLike) -> type[Any]: # 返回值类型为任意类型,通常用于 ctypes.Union 或 ctypes.Structure
def as_ctypes_type(dtype: str) -> type[Any]: # 返回值类型为任意类型,接受字符串参数
# 函数重载,将 ctypes 类型的对象转换为 NumPy 数组
def as_array(obj: ctypes._PointerLike, shape: Sequence[int]) -> NDArray[Any]: # 接受 ctypes 指针对象和形状参数,返回 NumPy 数组
def as_array(obj: _ArrayLike[_SCT], shape: None | _ShapeLike = ...) -> NDArray[_SCT]: # 接受数组对象和形状参数,返回 NumPy 数组
def as_array(obj: object, shape: None | _ShapeLike = ...) -> NDArray[Any]: # 接受任意对象和形状参数,返回 NumPy 数组
# 函数重载,将 NumPy 对象转换为对应的 ctypes 类型
def as_ctypes(obj: np.bool) -> ctypes.c_bool: # 将 NumPy 布尔值转换为 ctypes.c_bool 类型
def as_ctypes(obj: byte) -> ctypes.c_byte: # 将 NumPy 字节值转换为 ctypes.c_byte 类型
def as_ctypes(obj: short) -> ctypes.c_short: # 将 NumPy 短整数值转换为 ctypes.c_short 类型
def as_ctypes(obj: intc) -> ctypes.c_int: # 将 NumPy 整数值转换为 ctypes.c_int 类型
# 将 long 类型对象转换为 ctypes.c_long 类型
@overload
def as_ctypes(obj: long) -> ctypes.c_long: ...
# 将 longlong 类型对象转换为 ctypes.c_longlong 类型
@overload
def as_ctypes(obj: longlong) -> ctypes.c_longlong: ...
# 将 ubyte 类型对象转换为 ctypes.c_ubyte 类型
@overload
def as_ctypes(obj: ubyte) -> ctypes.c_ubyte: ...
# 将 ushort 类型对象转换为 ctypes.c_ushort 类型
@overload
def as_ctypes(obj: ushort) -> ctypes.c_ushort: ...
# 将 uintc 类型对象转换为 ctypes.c_uint 类型
@overload
def as_ctypes(obj: uintc) -> ctypes.c_uint: ...
# 将 ulong 类型对象转换为 ctypes.c_ulong 类型
@overload
def as_ctypes(obj: ulong) -> ctypes.c_ulong: ...
# 将 ulonglong 类型对象转换为 ctypes.c_ulonglong 类型
@overload
def as_ctypes(obj: ulonglong) -> ctypes.c_ulonglong: ...
# 将 single 类型对象转换为 ctypes.c_float 类型
@overload
def as_ctypes(obj: single) -> ctypes.c_float: ...
# 将 double 类型对象转换为 ctypes.c_double 类型
@overload
def as_ctypes(obj: double) -> ctypes.c_double: ...
# 将 longdouble 类型对象转换为 ctypes.c_longdouble 类型
@overload
def as_ctypes(obj: longdouble) -> ctypes.c_longdouble: ...
# 将 void 类型对象转换为 ctypes.Union 或 ctypes.Structure 类型
@overload
def as_ctypes(obj: void) -> Any: # `ctypes.Union` or `ctypes.Structure`
...
# 将 NDArray[np.bool] 类型对象转换为 ctypes.Array[ctypes.c_bool] 类型
@overload
def as_ctypes(obj: NDArray[np.bool]) -> ctypes.Array[ctypes.c_bool]: ...
# 将 NDArray[byte] 类型对象转换为 ctypes.Array[ctypes.c_byte] 类型
@overload
def as_ctypes(obj: NDArray[byte]) -> ctypes.Array[ctypes.c_byte]: ...
# 将 NDArray[short] 类型对象转换为 ctypes.Array[ctypes.c_short] 类型
@overload
def as_ctypes(obj: NDArray[short]) -> ctypes.Array[ctypes.c_short]: ...
# 将 NDArray[intc] 类型对象转换为 ctypes.Array[ctypes.c_int] 类型
@overload
def as_ctypes(obj: NDArray[intc]) -> ctypes.Array[ctypes.c_int]: ...
# 将 NDArray[long] 类型对象转换为 ctypes.Array[ctypes.c_long] 类型
@overload
def as_ctypes(obj: NDArray[long]) -> ctypes.Array[ctypes.c_long]: ...
# 将 NDArray[longlong] 类型对象转换为 ctypes.Array[ctypes.c_longlong] 类型
@overload
def as_ctypes(obj: NDArray[longlong]) -> ctypes.Array[ctypes.c_longlong]: ...
# 将 NDArray[ubyte] 类型对象转换为 ctypes.Array[ctypes.c_ubyte] 类型
@overload
def as_ctypes(obj: NDArray[ubyte]) -> ctypes.Array[ctypes.c_ubyte]: ...
# 将 NDArray[ushort] 类型对象转换为 ctypes.Array[ctypes.c_ushort] 类型
@overload
def as_ctypes(obj: NDArray[ushort]) -> ctypes.Array[ctypes.c_ushort]: ...
# 将 NDArray[uintc] 类型对象转换为 ctypes.Array[ctypes.c_uint] 类型
@overload
def as_ctypes(obj: NDArray[uintc]) -> ctypes.Array[ctypes.c_uint]: ...
# 将 NDArray[ulong] 类型对象转换为 ctypes.Array[ctypes.c_ulong] 类型
@overload
def as_ctypes(obj: NDArray[ulong]) -> ctypes.Array[ctypes.c_ulong]: ...
# 将 NDArray[ulonglong] 类型对象转换为 ctypes.Array[ctypes.c_ulonglong] 类型
@overload
def as_ctypes(obj: NDArray[ulonglong]) -> ctypes.Array[ctypes.c_ulonglong]: ...
# 将 NDArray[single] 类型对象转换为 ctypes.Array[ctypes.c_float] 类型
@overload
def as_ctypes(obj: NDArray[single]) -> ctypes.Array[ctypes.c_float]: ...
# 将 NDArray[double] 类型对象转换为 ctypes.Array[ctypes.c_double] 类型
@overload
def as_ctypes(obj: NDArray[double]) -> ctypes.Array[ctypes.c_double]: ...
# 将 NDArray[longdouble] 类型对象转换为 ctypes.Array[ctypes.c_longdouble] 类型
@overload
def as_ctypes(obj: NDArray[longdouble]) -> ctypes.Array[ctypes.c_longdouble]: ...
# 将 NDArray[void] 类型对象转换为 ctypes.Array[Any] 类型,可能是 `ctypes.Union` 或 `ctypes.Structure`
@overload
def as_ctypes(obj: NDArray[void]) -> ctypes.Array[Any]: ...
.\numpy\numpy\distutils\armccompiler.py
from distutils.unixccompiler import UnixCCompiler
class ArmCCompiler(UnixCCompiler):
"""
Arm compiler subclass inheriting from UnixCCompiler.
"""
# 设置编译器类型为 'arm'
compiler_type = 'arm'
# 设置 C 编译器可执行文件为 'armclang'
cc_exe = 'armclang'
# 设置 C++ 编译器可执行文件为 'armclang++'
cxx_exe = 'armclang++'
def __init__(self, verbose=0, dry_run=0, force=0):
# 调用父类 UnixCCompiler 的初始化方法
UnixCCompiler.__init__(self, verbose, dry_run, force)
# 将 C 编译器可执行文件保存到 cc_compiler 变量中
cc_compiler = self.cc_exe
# 将 C++ 编译器可执行文件保存到 cxx_compiler 变量中
cxx_compiler = self.cxx_exe
# 设置编译器的各种参数及选项,以及链接器的选项
self.set_executables(
compiler=cc_compiler + ' -O3 -fPIC', # 设置编译器命令及优化级别和位置无关代码
compiler_so=cc_compiler + ' -O3 -fPIC', # 设置用于编译源文件的编译器命令及优化级别和位置无关代码
compiler_cxx=cxx_compiler + ' -O3 -fPIC', # 设置用于编译 C++ 源文件的编译器命令及优化级别和位置无关代码
linker_exe=cc_compiler + ' -lamath', # 设置用于链接可执行文件的链接器命令及链接数学库
linker_so=cc_compiler + ' -lamath -shared' # 设置用于链接共享库的链接器命令及链接数学库和共享标志
)
.\numpy\numpy\distutils\ccompiler.py
import os
import re
import sys
import platform
import shlex
import time
import subprocess
from copy import copy
from pathlib import Path
from distutils import ccompiler
from distutils.ccompiler import (
compiler_class, gen_lib_options, get_default_compiler, new_compiler,
CCompiler
)
from distutils.errors import (
DistutilsExecError, DistutilsModuleError, DistutilsPlatformError,
CompileError, UnknownFileError
)
from distutils.sysconfig import customize_compiler
from distutils.version import LooseVersion
from numpy.distutils import log
from numpy.distutils.exec_command import (
filepath_from_subprocess_output, forward_bytes_to_stdout
)
from numpy.distutils.misc_util import cyg2win32, is_sequence, mingw32, \
get_num_build_jobs, \
_commandline_dep_string, \
sanitize_cxx_flags
# globals for parallel build management
import threading
_job_semaphore = None
_global_lock = threading.Lock()
_processing_files = set()
def _needs_build(obj, cc_args, extra_postargs, pp_opts):
"""
Check if an objects needs to be rebuild based on its dependencies
Parameters
----------
obj : str
object file
Returns
-------
bool
True if the object needs to be rebuilt, False otherwise
"""
# defined in unixcompiler.py
dep_file = obj + '.d'
# 如果依赖文件不存在,则需要重新构建
if not os.path.exists(dep_file):
return True
# dep_file 是一个包含 'object: dependencies' 格式的 makefile
# 最后一行包含编译器命令行参数,因为某些项目可能会使用不同参数多次编译扩展
with open(dep_file) as f:
lines = f.readlines()
# 生成当前编译器命令行的字符串表示
cmdline = _commandline_dep_string(cc_args, extra_postargs, pp_opts)
last_cmdline = lines[-1]
# 如果最后一行命令行与当前命令行不同,则需要重新构建
if last_cmdline != cmdline:
return True
contents = ''.join(lines[:-1])
# 解析出依赖文件中的依赖项
deps = [x for x in shlex.split(contents, posix=True)
if x != "\n" and not x.endswith(":")]
try:
t_obj = os.stat(obj).st_mtime
# 检查是否有任何依赖文件比对象文件更新
# 依赖项包括用于创建对象的源文件
for f in deps:
if os.stat(f).st_mtime > t_obj:
return True
except OSError:
# 如果发生 OSError,则认为需要重新构建(理论上不应发生)
return True
# 如果以上条件均不满足,则不需要重新构建
return False
def replace_method(klass, method_name, func):
"""
Replace a method in a class dynamically.
Parameters
----------
klass : class
The class in which the method will be replaced.
method_name : str
The name of the method to be replaced.
func : function
The replacement function.
Notes
-----
This function dynamically replaces a method in a class with
another function.
"""
# Py3k 不再具有未绑定方法,MethodType 不起作用,因此使用 lambda 表达式来替换方法
m = lambda self, *args, **kw: func(self, *args, **kw)
setattr(klass, method_name, m)
######################################################################
## Method that subclasses may redefine. But don't call this method,
## it is private to CCompiler class and may return unexpected
## results if used elsewhere. So, you have been warned..
def CCompiler_find_executables(self):
"""
Placeholder method intended for redefinition by subclasses.
Notes
-----
This method is intended to be redefined by subclasses of CCompiler.
Calling this method directly outside its intended context (subclasses
of CCompiler) may yield unexpected results.
"""
"""
这是一个空函数,它本身不执行任何操作,但可以被`get_version`方法调用,并且可以被子类重写。
特别是在`FCompiler`类中重新定义了这个方法,并且那里可以找到更多的文档说明。
"""
pass
# 替换 CCompiler 类的 find_executables 方法为 CCompiler_find_executables 方法
replace_method(CCompiler, 'find_executables', CCompiler_find_executables)
# 使用定制的 CCompiler.spawn 方法执行命令的子进程操作。
def CCompiler_spawn(self, cmd, display=None, env=None):
"""
在子进程中执行命令。
Parameters
----------
cmd : str
要执行的命令。
display : str 或者 str 序列,可选
要添加到 `numpy.distutils` 日志文件的文本。如果未提供,则 `display` 等于 `cmd`。
env : 字典类型的环境变量,可选
Returns
-------
None
Raises
------
DistutilsExecError
如果命令失败,即退出状态不为 0。
"""
# 如果环境变量未提供,则使用当前操作系统的环境变量
env = env if env is not None else dict(os.environ)
# 如果没有指定显示文本,则使用命令本身作为显示文本
if display is None:
display = cmd
# 如果显示文本是序列,则将其连接为字符串
if is_sequence(display):
display = ' '.join(list(display))
# 记录信息到日志
log.info(display)
try:
# 如果设置了详细输出,直接执行命令
if self.verbose:
subprocess.check_output(cmd, env=env)
else:
# 否则,将错误输出重定向到标准输出
subprocess.check_output(cmd, stderr=subprocess.STDOUT, env=env)
except subprocess.CalledProcessError as exc:
# 如果命令执行失败,捕获异常
o = exc.output
s = exc.returncode
except OSError as e:
# 如果出现 OSError,处理异常情况
o = f"\n\n{e}\n\n\n"
try:
# 尝试根据当前系统的编码方式编码输出
o = o.encode(sys.stdout.encoding)
except AttributeError:
o = o.encode('utf8')
# 设置返回状态为 127
s = 127
else:
# 如果没有异常,则返回 None
return None
# 如果命令是序列,则将其连接为字符串
if is_sequence(cmd):
cmd = ' '.join(list(cmd))
# 如果设置了详细输出,将输出传递到标准输出
if self.verbose:
forward_bytes_to_stdout(o)
# 如果输出包含 'Too many open files',建议重新运行设置命令直至成功
if re.search(b'Too many open files', o):
msg = '\nTry rerunning setup command until build succeeds.'
else:
msg = ''
# 抛出 DistutilsExecError 异常,指明命令执行失败及其退出状态
raise DistutilsExecError('Command "%s" failed with exit status %d%s' %
(cmd, s, msg))
# 替换 CCompiler 类的 spawn 方法为 CCompiler_spawn 方法
replace_method(CCompiler, 'spawn', CCompiler_spawn)
# 定义 CCompiler 类的 object_filenames 方法
def CCompiler_object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
"""
返回给定源文件的对象文件名。
Parameters
----------
source_filenames : str 列表
源文件路径的列表。路径可以是相对路径或绝对路径,这会透明处理。
strip_dir : bool, 可选
是否从返回的路径中剥离目录。如果为 True,则返回文件名加上 `output_dir`。默认为 False。
output_dir : str, 可选
输出目录的路径。
"""
# 如果未提供输出目录,则设为空字符串
if output_dir is None:
output_dir = ''
# 初始化空列表用于存储目标文件路径
obj_names = []
# 遍历每个源文件名
for src_name in source_filenames:
# 分离文件名和扩展名,并规范化路径
base, ext = os.path.splitext(os.path.normpath(src_name))
# 如果路径包含驱动器信息,去除驱动器部分
base = os.path.splitdrive(base)[1] # Chop off the drive
# 如果路径是绝对路径,则去除开头的 '/'
base = base[os.path.isabs(base):] # If abs, chop off leading /
# 处理以 '..' 开头的相对路径部分
if base.startswith('..'):
# 解析开头的相对路径部分,os.path.normpath 已经处理了中间的部分
i = base.rfind('..')+2
d = base[:i]
# 获取绝对路径的基本名称
d = os.path.basename(os.path.abspath(d))
base = d + base[i:]
# 如果文件扩展名不在支持的源文件扩展名列表中,抛出异常
if ext not in self.src_extensions:
raise UnknownFileError("unknown file type '%s' (from '%s')" % (ext, src_name))
# 如果 strip_dir 为 True,则只保留基本文件名部分
if strip_dir:
base = os.path.basename(base)
# 构建目标文件的完整路径,结合输出目录和目标文件的扩展名
obj_name = os.path.join(output_dir, base + self.obj_extension)
# 将目标文件路径添加到列表中
obj_names.append(obj_name)
# 返回所有目标文件路径的列表
return obj_names
# 替换 CCompiler 类的 'object_filenames' 方法为自定义的 'CCompiler_object_filenames'
replace_method(CCompiler, 'object_filenames', CCompiler_object_filenames)
# 定义 CCompiler_compile 方法,用于编译一个或多个源文件
def CCompiler_compile(self, sources, output_dir=None, macros=None,
include_dirs=None, debug=0, extra_preargs=None,
extra_postargs=None, depends=None):
"""
Compile one or more source files.
Please refer to the Python distutils API reference for more details.
Parameters
----------
sources : list of str
A list of filenames
output_dir : str, optional
Path to the output directory.
macros : list of tuples
A list of macro definitions.
include_dirs : list of str, optional
The directories to add to the default include file search path for
this compilation only.
debug : bool, optional
Whether or not to output debug symbols in or alongside the object
file(s).
extra_preargs, extra_postargs : ?
Extra pre- and post-arguments.
depends : list of str, optional
A list of file names that all targets depend on.
Returns
-------
objects : list of str
A list of object file names, one per source file `sources`.
Raises
------
CompileError
If compilation fails.
"""
# 获取并设置并行编译任务的数量
global _job_semaphore
jobs = get_num_build_jobs()
# 设置信号量以限制并行编译任务的数量(适用于 Python >= 3.5 的扩展级并行编译)
with _global_lock:
if _job_semaphore is None:
_job_semaphore = threading.Semaphore(jobs)
# 如果没有源文件,则返回空列表
if not sources:
return []
# 导入所需的编译器类和模块
from numpy.distutils.fcompiler import (FCompiler,
FORTRAN_COMMON_FIXED_EXTENSIONS,
has_f90_header)
# 如果 self 是 FCompiler 类的实例
if isinstance(self, FCompiler):
# 显示 Fortran 编译器的相关信息
display = []
for fc in ['f77', 'f90', 'fix']:
fcomp = getattr(self, 'compiler_'+fc)
if fcomp is None:
continue
display.append("Fortran %s compiler: %s" % (fc, ' '.join(fcomp)))
display = '\n'.join(display)
else:
# 显示 C 编译器的相关信息
ccomp = self.compiler_so
display = "C compiler: %s\n" % (' '.join(ccomp),)
# 记录编译器相关信息到日志中
log.info(display)
# 设置编译环境并获取编译所需的宏定义、对象文件名、额外的后处理参数、预处理选项和构建对象
macros, objects, extra_postargs, pp_opts, build = \
self._setup_compile(output_dir, macros, include_dirs, sources,
depends, extra_postargs)
# 获取 C 编译器的命令行参数
cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
# 显示编译选项到日志中
display = "compile options: '%s'" % (' '.join(cc_args))
if extra_postargs:
display += "\nextra options: '%s'" % (' '.join(extra_postargs))
# 记录编译选项到日志中
log.info(display)
def single_compile(args):
obj, (src, ext) = args
if not _needs_build(obj, cc_args, extra_postargs, pp_opts):
return
# 检查是否需要构建该目标文件
while True:
# 需要使用显式锁,因为 GIL 无法进行原子性的检查和修改操作
with _global_lock:
# 如果目标文件当前没有在处理中,则开始处理
if obj not in _processing_files:
_processing_files.add(obj)
break
# 等待处理结束
time.sleep(0.1)
try:
# 从作业信号量中获取插槽并进行编译
with _job_semaphore:
self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
finally:
# 注册处理完成
with _global_lock:
_processing_files.remove(obj)
if isinstance(self, FCompiler):
objects_to_build = list(build.keys())
f77_objects, other_objects = [], []
for obj in objects:
if obj in objects_to_build:
src, ext = build[obj]
if self.compiler_type=='absoft':
obj = cyg2win32(obj)
src = cyg2win32(src)
if Path(src).suffix.lower() in FORTRAN_COMMON_FIXED_EXTENSIONS \
and not has_f90_header(src):
# 将需要用 Fortran 77 编译的对象添加到列表中
f77_objects.append((obj, (src, ext)))
else:
# 将需要用其他编译器编译的对象添加到列表中
other_objects.append((obj, (src, ext)))
# Fortran 77 对象可以并行构建
build_items = f77_objects
# 串行构建 Fortran 90 模块,模块文件在编译期间生成,并可能被列表中后续文件使用,因此顺序很重要
for o in other_objects:
single_compile(o)
else:
build_items = build.items()
if len(build) > 1 and jobs > 1:
# 并行构建
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(jobs) as pool:
res = pool.map(single_compile, build_items)
list(res) # 访问结果以引发错误
else:
# 串行构建
for o in build_items:
single_compile(o)
# 返回所有目标文件名,而不仅仅是刚刚构建的那些
return objects
# 替换 CCompiler 类的 'compile' 方法为自定义的 CCompiler_compile 方法
replace_method(CCompiler, 'compile', CCompiler_compile)
def CCompiler_customize_cmd(self, cmd, ignore=()):
"""
自定义编译器使用 distutils 命令。
Parameters
----------
cmd : class instance
继承自 ``distutils.cmd.Command`` 的实例。
ignore : sequence of str, optional
不应更改的 ``distutils.ccompiler.CCompiler`` 命令(不包括 ``'set_'``)的列表。检查的字符串有:
``('include_dirs', 'define', 'undef', 'libraries', 'library_dirs',
'rpath', 'link_objects')``。
Returns
-------
None
"""
# 记录日志,显示正在使用哪个类定制编译器
log.info('customize %s using %s' % (self.__class__.__name__,
cmd.__class__.__name__))
# 如果 self 中有 'compiler' 属性并且第一个元素是 'clang',且不是在 arm64 架构的 macOS 上
if (
hasattr(self, 'compiler') and
'clang' in self.compiler[0] and
not (platform.machine() == 'arm64' and sys.platform == 'darwin')
):
# clang 默认使用非严格的浮点错误点模型。
# 但是,macosx_arm64 目前不支持 '-ftrapping-math'(2023-04-08)。
# 因为 NumPy 和大多数 Python 库会对此发出警告,所以进行覆盖:
self.compiler.append('-ftrapping-math')
self.compiler_so.append('-ftrapping-math')
# 定义一个函数 allow,用于检查命令是否允许对应的操作
def allow(attr):
return getattr(cmd, attr, None) is not None and attr not in ignore
# 根据命令是否允许特定操作,设置相应的 include_dirs、define、undef、libraries、library_dirs、rpath 和 link_objects
if allow('include_dirs'):
self.set_include_dirs(cmd.include_dirs)
if allow('define'):
for (name, value) in cmd.define:
self.define_macro(name, value)
if allow('undef'):
for macro in cmd.undef:
self.undefine_macro(macro)
if allow('libraries'):
self.set_libraries(self.libraries + cmd.libraries)
if allow('library_dirs'):
self.set_library_dirs(self.library_dirs + cmd.library_dirs)
if allow('rpath'):
self.set_runtime_library_dirs(cmd.rpath)
if allow('link_objects'):
self.set_link_objects(cmd.link_objects)
# 替换 CCompiler 类的 'customize_cmd' 方法为自定义的 CCompiler_customize_cmd 方法
replace_method(CCompiler, 'customize_cmd', CCompiler_customize_cmd)
def _compiler_to_string(compiler):
props = []
mx = 0
keys = list(compiler.executables.keys())
# 检查需要的键是否存在,如果不存在则添加到 keys 列表中
for key in ['version', 'libraries', 'library_dirs',
'object_switch', 'compile_switch',
'include_dirs', 'define', 'undef', 'rpath', 'link_objects']:
if key not in keys:
keys.append(key)
# 遍历 keys 中的每个键,获取编译器对象的属性值,并格式化为字符串
for key in keys:
if hasattr(compiler, key):
v = getattr(compiler, key)
mx = max(mx, len(key))
props.append((key, repr(v)))
# 格式化输出属性,并使用换行符连接成一个字符串
fmt = '%-' + repr(mx+1) + 's = %s'
lines = [fmt % prop for prop in props]
return '\n'.join(lines)
def CCompiler_show_customization(self):
"""
打印编译器的定制信息到标准输出。
Parameters
----------
None
Returns
-------
None
Notes
-----
仅在 distutils 日志阈值 < 2 时执行打印操作。
"""
try:
# 获取编译器版本信息
self.get_version()
# 捕获并忽略任何异常,不对异常进行处理
except Exception:
pass
# 如果全局日志对象 log 的阈值小于 2
if log._global_log.threshold < 2:
# 打印 80 个 '*' 字符,用于分隔线
print('*'*80)
# 打印当前对象的类名
print(self.__class__)
# 打印通过 _compiler_to_string 函数编译后的对象表示
print(_compiler_to_string(self))
# 打印 80 个 '*' 字符,用于分隔线
print('*'*80)
# 替换 CCompiler 类的 show_customization 方法为 CCompiler_show_customization 函数
replace_method(CCompiler, 'show_customization', CCompiler_show_customization)
# 自定义编译器实例的平台特定定制
def CCompiler_customize(self, dist, need_cxx=0):
"""
Do any platform-specific customization of a compiler instance.
This method calls ``distutils.sysconfig.customize_compiler`` for
platform-specific customization, as well as optionally remove a flag
to suppress spurious warnings in case C++ code is being compiled.
Parameters
----------
dist : object
This parameter is not used for anything.
need_cxx : bool, optional
Whether or not C++ has to be compiled. If True, the
``"-Wstrict-prototypes"`` option is removed to prevent spurious
warnings. Default is False.
Returns
-------
None
Notes
-----
All the default options used by distutils can be extracted with::
from distutils import sysconfig
sysconfig.get_config_vars('CC', 'CXX', 'OPT', 'BASECFLAGS',
'CCSHARED', 'LDSHARED', 'SO')
"""
# 输出日志信息,标明正在定制的编译器类名
log.info('customize %s' % (self.__class__.__name__))
# 调用外部定义的 customize_compiler 函数,对编译器实例进行定制
customize_compiler(self)
# 如果需要编译 C++ 代码
if need_cxx:
# 一般情况下,distutils 使用 -Wstrict-prototypes 选项,但该选项只对 C 代码有效,对 C++ 代码无效
# 如果存在该选项,则移除,以避免每次编译时出现误报警告
try:
self.compiler_so.remove('-Wstrict-prototypes')
except (AttributeError, ValueError):
pass
# 如果编译器存在,并且使用的是 cc 编译器
if hasattr(self, 'compiler') and 'cc' in self.compiler[0]:
# 如果编译器没有设置 compiler_cxx 属性,并且编译器以 'gcc' 开头,则替换为 'g++'
if not self.compiler_cxx:
if self.compiler[0].startswith('gcc'):
a, b = 'gcc', 'g++'
else:
a, b = 'cc', 'c++'
# 设置 compiler_cxx 属性为经过修正的编译器名称及其参数
self.compiler_cxx = [self.compiler[0].replace(a, b)]\
+ self.compiler[1:]
else:
# 如果编译器存在,但没有设置 compiler_cxx 属性,则记录警告信息
if hasattr(self, 'compiler'):
log.warn("#### %s #######" % (self.compiler,))
if not hasattr(self, 'compiler_cxx'):
log.warn('Missing compiler_cxx fix for ' + self.__class__.__name__)
# 检查编译器是否支持类似 gcc 风格的自动依赖性
# 在每个扩展上运行,因此对于已知的好编译器跳过
if hasattr(self, 'compiler') and ('gcc' in self.compiler[0] or
'g++' in self.compiler[0] or
'clang' in self.compiler[0]):
# 设置 _auto_depends 属性为 True,表示支持自动依赖性
self._auto_depends = True
elif os.name == 'posix':
# 如果操作系统为 POSIX 类型(Unix/Linux),执行以下操作
import tempfile # 导入临时文件模块
import shutil # 导入文件操作模块
# 创建临时目录
tmpdir = tempfile.mkdtemp()
try:
# 在临时目录下创建一个名为 "file.c" 的文件,并写入内容 "int a;\n"
fn = os.path.join(tmpdir, "file.c")
with open(fn, "w") as f:
f.write("int a;\n")
# 调用 self.compile 方法,编译文件 fn,并指定输出目录为 tmpdir,
# 额外的编译参数为 ['-MMD', '-MF', fn + '.d']
self.compile([fn], output_dir=tmpdir,
extra_preargs=['-MMD', '-MF', fn + '.d'])
# 设置标志以表示自动依赖项处理已启用
self._auto_depends = True
except CompileError:
# 如果编译过程中出现 CompileError 异常,则表示自动依赖项处理未能启用
self._auto_depends = False
finally:
# 无论是否发生异常,都要删除临时目录及其内容
shutil.rmtree(tmpdir)
return
# 使用动态方法替换 CCompiler 类的 'customize' 方法为 CCompiler_customize 函数
replace_method(CCompiler, 'customize', CCompiler_customize)
# 定义一个简单的版本号匹配函数,用于 CCompiler 和 FCompiler
def simple_version_match(pat=r'[-.\d]+', ignore='', start=''):
"""
Simple matching of version numbers, for use in CCompiler and FCompiler.
Parameters
----------
pat : str, optional
A regular expression matching version numbers.
Default is ``r'[-.\d]+'``.
ignore : str, optional
A regular expression matching patterns to skip.
Default is ``''``, in which case nothing is skipped.
start : str, optional
A regular expression matching the start of where to start looking
for version numbers.
Default is ``''``, in which case searching is started at the
beginning of the version string given to `matcher`.
Returns
-------
matcher : callable
A function that is appropriate to use as the ``.version_match``
attribute of a ``distutils.ccompiler.CCompiler`` class. `matcher` takes a single parameter,
a version string.
"""
def matcher(self, version_string):
# version string may appear in the second line, so getting rid
# of new lines:
version_string = version_string.replace('\n', ' ')
pos = 0
if start:
m = re.match(start, version_string)
if not m:
return None
pos = m.end()
while True:
m = re.search(pat, version_string[pos:])
if not m:
return None
if ignore and re.match(ignore, m.group(0)):
pos = m.end()
continue
break
return m.group(0)
return matcher
# 定义 CCompiler 类的 get_version 方法
def CCompiler_get_version(self, force=False, ok_status=[0]):
"""
Return compiler version, or None if compiler is not available.
Parameters
----------
force : bool, optional
If True, force a new determination of the version, even if the
compiler already has a version attribute. Default is False.
ok_status : list of int, optional
The list of status values returned by the version look-up process
for which a version string is returned. If the status value is not
in `ok_status`, None is returned. Default is ``[0]``.
Returns
-------
version : str or None
Version string, in the format of ``distutils.version.LooseVersion``.
"""
if not force and hasattr(self, 'version'):
return self.version
# 查找可执行文件
self.find_executables()
try:
version_cmd = self.version_cmd
except AttributeError:
return None
if not version_cmd or not version_cmd[0]:
return None
try:
# 使用之前定义的版本号匹配器
matcher = self.version_match
# 捕获 AttributeError 异常,如果捕获到则执行以下代码块
except AttributeError:
try:
# 尝试获取 self.version_pattern 属性
pat = self.version_pattern
except AttributeError:
# 如果未定义 self.version_pattern 属性,则返回 None
return None
# 定义 matcher 函数,用于匹配版本字符串
def matcher(version_string):
# 使用正则表达式匹配版本字符串
m = re.match(pat, version_string)
if not m:
return None
# 获取匹配的版本号部分
version = m.group('version')
return version
try:
# 执行外部命令并获取输出,标准错误输出被合并到标准输出中
output = subprocess.check_output(version_cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
# 如果外部命令抛出异常,则获取异常的输出和返回码
output = exc.output
status = exc.returncode
except OSError:
# 捕获 OSError 异常,表示操作系统错误
# 匹配历史返回值,与 exec_command() 捕获的父异常类似
status = 127
output = b''
else:
# 如果外部命令执行成功,则处理输出,这里先将输出视为文件路径
output = filepath_from_subprocess_output(output)
status = 0
# 初始化版本号为 None
version = None
# 如果返回码在允许的状态码列表中
if status in ok_status:
# 使用 matcher 函数匹配输出的版本号
version = matcher(output)
# 如果成功匹配到版本号,则转换为松散版本号对象
if version:
version = LooseVersion(version)
# 将匹配到的版本号赋值给 self.version
self.version = version
# 返回版本号
return version
# 将 CCompiler 类的 get_version 方法替换为 CCompiler_get_version 方法
replace_method(CCompiler, 'get_version', CCompiler_get_version)
# 定义 CCompiler 类的 cxx_compiler 方法,返回 C++ 编译器实例
def CCompiler_cxx_compiler(self):
"""
Return the C++ compiler.
Parameters
----------
None
Returns
-------
cxx : class instance
The C++ compiler, as a ``distutils.ccompiler.CCompiler`` instance.
"""
# 如果编译器类型是 'msvc', 'intelw', 'intelemw' 中的一种,则返回当前实例
if self.compiler_type in ('msvc', 'intelw', 'intelemw'):
return self
# 复制当前实例,以便对其进行修改
cxx = copy(self)
# 设置 C++ 编译器和编译选项
cxx.compiler_cxx = cxx.compiler_cxx
cxx.compiler_so = [cxx.compiler_cxx[0]] + \
sanitize_cxx_flags(cxx.compiler_so[1:])
# 如果运行在 'aix' 或 'os400' 平台上,并且链接器使用的是 ld_so_aix 脚本
if (sys.platform.startswith(('aix', 'os400')) and
'ld_so_aix' in cxx.linker_so[0]):
# AIX 需要包含 Python 提供的 ld_so_aix 脚本
cxx.linker_so = [cxx.linker_so[0], cxx.compiler_cxx[0]] \
+ cxx.linker_so[2:]
# 如果运行在 'os400' 平台上,添加特定的编译选项
if sys.platform.startswith('os400'):
# 这是为了在 i 7.4 及其以前版本中支持 printf() 中的 PRId64
cxx.compiler_so.append('-D__STDC_FORMAT_MACROS')
# 这是 gcc 10.3 的一个 bug,处理 TLS 初始化失败的情况
cxx.compiler_so.append('-fno-extern-tls-init')
cxx.linker_so.append('-fno-extern-tls-init')
else:
# 否则,将链接器设置为与 C++ 编译器相同
cxx.linker_so = [cxx.compiler_cxx[0]] + cxx.linker_so[1:]
# 返回修改后的 C++ 编译器实例
return cxx
# 将 CCompiler 类的 cxx_compiler 方法替换为 CCompiler_cxx_compiler 方法
replace_method(CCompiler, 'cxx_compiler', CCompiler_cxx_compiler)
# 定义支持的编译器类别和相关描述
compiler_class['intel'] = ('intelccompiler', 'IntelCCompiler',
"Intel C Compiler for 32-bit applications")
compiler_class['intele'] = ('intelccompiler', 'IntelItaniumCCompiler',
"Intel C Itanium Compiler for Itanium-based applications")
compiler_class['intelem'] = ('intelccompiler', 'IntelEM64TCCompiler',
"Intel C Compiler for 64-bit applications")
compiler_class['intelw'] = ('intelccompiler', 'IntelCCompilerW',
"Intel C Compiler for 32-bit applications on Windows")
compiler_class['intelemw'] = ('intelccompiler', 'IntelEM64TCCompilerW',
"Intel C Compiler for 64-bit applications on Windows")
compiler_class['pathcc'] = ('pathccompiler', 'PathScaleCCompiler',
"PathScale Compiler for SiCortex-based applications")
compiler_class['arm'] = ('armccompiler', 'ArmCCompiler',
"Arm C Compiler")
compiler_class['fujitsu'] = ('fujitsuccompiler', 'FujitsuCCompiler',
"Fujitsu C Compiler")
# 将默认编译器设置添加到 ccompiler._default_compilers 元组中
ccompiler._default_compilers += (('linux.*', 'intel'),
('linux.*', 'intele'),
('linux.*', 'intelem'),
('linux.*', 'pathcc'),
('nt', 'intelw'),
('nt', 'intelemw'))
# 如果运行在 Windows 平台上
if sys.platform == 'win32':
# 将键 'mingw32' 映射到一个元组,包含编译器模块名、编译器类名、描述信息
compiler_class['mingw32'] = ('mingw32ccompiler', 'Mingw32CCompiler',
"Mingw32 port of GNU C Compiler for Win32"
"(for MSC built Python)")
# 如果当前系统是 mingw32 平台
if mingw32():
# 在 Windows 平台上,默认使用 mingw32(gcc)作为编译器,
# 因为 MSVC 无法构建 blitz 相关的内容。
log.info('Setting mingw32 as default compiler for nt.')
# 设置 mingw32 为 nt 平台的默认编译器,并保留原有默认编译器列表
ccompiler._default_compilers = (('nt', 'mingw32'),) \
+ ccompiler._default_compilers
# 将全局变量 _distutils_new_compiler 指向函数 new_compiler
_distutils_new_compiler = new_compiler
# 定义函数 new_compiler,用于创建一个新的编译器对象
def new_compiler (plat=None,
compiler=None,
verbose=None,
dry_run=0,
force=0):
# 如果未提供 verbose 参数,则根据日志级别设置 verbose
if verbose is None:
verbose = log.get_threshold() <= log.INFO
# 如果未提供 plat 参数,则使用当前操作系统的名称
if plat is None:
plat = os.name
try:
# 如果未指定 compiler,则获取默认的编译器
if compiler is None:
compiler = get_default_compiler(plat)
# 根据 compiler 获取对应的模块名、类名和描述
(module_name, class_name, long_description) = compiler_class[compiler]
except KeyError:
# 如果未知平台或编译器,则抛出异常
msg = "don't know how to compile C/C++ code on platform '%s'" % plat
if compiler is not None:
msg = msg + " with '%s' compiler" % compiler
raise DistutilsPlatformError(msg)
# 组装模块名
module_name = "numpy.distutils." + module_name
try:
# 尝试导入模块 module_name
__import__ (module_name)
except ImportError as e:
# 如果导入失败,则记录错误信息,并尝试从 distutils 中导入
msg = str(e)
log.info('%s in numpy.distutils; trying from distutils',
str(msg))
module_name = module_name[6:]
try:
__import__(module_name)
except ImportError as e:
# 如果两次导入均失败,则抛出模块加载异常
msg = str(e)
raise DistutilsModuleError("can't compile C/C++ code: unable to load module '%s'" % \
module_name)
try:
# 尝试获取导入后的模块和类
module = sys.modules[module_name]
klass = vars(module)[class_name]
except KeyError:
# 如果未找到对应类,则抛出模块异常
raise DistutilsModuleError(("can't compile C/C++ code: unable to find class '%s' " +
"in module '%s'") % (class_name, module_name))
# 使用找到的类创建编译器对象
compiler = klass(None, dry_run, force)
compiler.verbose = verbose
# 记录调试信息
log.debug('new_compiler returns %s' % (klass))
return compiler
# 将 ccompiler 模块的 new_compiler 函数指向上面定义的 new_compiler 函数
ccompiler.new_compiler = new_compiler
# 将全局变量 _distutils_gen_lib_options 指向 gen_lib_options 函数
_distutils_gen_lib_options = gen_lib_options
# 定义函数 gen_lib_options,用于生成库选项列表
def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries):
# 使用 _distutils_gen_lib_options 函数生成原始的库选项列表 r
r = _distutils_gen_lib_options(compiler, library_dirs,
runtime_library_dirs, libraries)
lib_opts = []
# 遍历 r 中的每一项,将其扩展成 lib_opts 列表
for i in r:
if is_sequence(i): # 如果 i 是序列,则展开后加入 lib_opts
lib_opts.extend(list(i))
else: # 否则直接添加到 lib_opts
lib_opts.append(i)
return lib_opts
# 将 ccompiler 模块的 gen_lib_options 函数指向上面定义的 gen_lib_options 函数
ccompiler.gen_lib_options = gen_lib_options
# 对于一些特定的编译器模块,将其 gen_lib_options 函数指向上面定义的 gen_lib_options 函数
# 这些模块包括 'msvc9', 'msvc', '_msvc', 'bcpp', 'cygwinc', 'emxc', 'unixc'
for _cc in ['msvc9', 'msvc', '_msvc', 'bcpp', 'cygwinc', 'emxc', 'unixc']:
_m = sys.modules.get('distutils.' + _cc + 'compiler')
if _m is not None:
setattr(_m, 'gen_lib_options', gen_lib_options)