类型和签名
理由
作为一个优化编译器,Numba 需要确定每个变量的类型以生成高效的机器代码。Python 的标准类型对于这一点来说不够精确,因此我们不得不开发自己的细粒度类型系统。
当你试图检查Numba的类型推断结果时,主要会遇到Numba类型,用于 调试 或 教育 目的。然而,如果你在 提前编译 代码时,需要明确使用类型。
签名
签名指定了一个函数的类型。具体允许哪种类型的签名取决于上下文(AOT 或 JIT 编译),但签名总是涉及一些 Numba 类型的表示,以指定函数参数的具体类型,以及在需要时指定函数的返回类型。
一个示例函数签名可以是字符串 "f8(i4, i4)"
(或等效的 "float64(int32, int32)"
),它指定了一个接受两个32位整数并返回一个双精度浮点数的函数。
基本类型
最基本的类型可以通过简单的表达式来表示。下面的符号指的是主 numba
模块的属性(因此,如果你看到“布尔”,它意味着该符号可以通过 numba.boolean
访问)。许多类型既可以通过规范名称也可以通过简写别名来使用,遵循 NumPy 的惯例。
数字
下表包含了 Numba 目前定义的基本数值类型及其别名。
类型名称 |
简写 |
注释 |
---|---|---|
布尔 |
b1 |
表示为一个字节 |
uint8, 字节 |
u1 |
8位无符号字节 |
uint16 |
u2 |
16位无符号整数 |
uint32 |
u4 |
32位无符号整数 |
uint64 |
u8 |
64位无符号整数 |
int8, char |
i1 |
8位有符号字节 |
int16 |
i2 |
16位有符号整数 |
int32 |
i4 |
32位有符号整数 |
int64 |
i8 |
64位有符号整数 |
intc |
– |
C 整型大小整数 |
uintc |
– |
C 语言中大小为整型的无符号整数 |
intp |
– |
指针大小的整数 |
uintp |
– |
指针大小的无符号整数 |
ssize_t |
– |
C ssize_t |
size_t |
– |
C size_t |
float32 |
f4 |
单精度浮点数 |
float64, 双精度 |
f8 |
双精度浮点数 |
complex64 |
c8 |
单精度复数 |
complex128 |
c16 |
双精度复数 |
数组
声明 Array
类型的简单方法是根据维数对基本类型进行下标。例如,一个单精度的一维数组:
>>> numba.float32[:]
array(float32, 1d, A)
或相同基础类型的三维数组:
>>> numba.float32[:, :, :]
array(float32, 3d, A)
此语法定义了没有特定布局的数组类型(生成的代码接受非连续和连续数组),但您可以通过在索引规范的开头或结尾使用 ::1
索引来指定特定的连续性:
>>> numba.float32[::1]
array(float32, 1d, C)
>>> numba.float32[:, :, ::1]
array(float32, 3d, C)
>>> numba.float32[::1, :, :]
array(float32, 3d, F)
这种类型的声明在 Numba 编译函数中是支持的,例如声明 typed.List 的类型。:
from numba import njit, types, typed
@njit
def example():
return typed.List.empty_list(types.float64[:, ::1])
请注意,此功能仅支持简单的数值类型。不支持应用于复合类型,例如记录类型。
函数
警告
将函数视为一等类型对象的功能正在开发中。
函数通常被认为是将输入参数转换为输出值的某种变换。在 Numba JIT 编译的函数中,函数也可以被视为对象,也就是说,函数除了可以被调用之外,还可以作为参数传递、作为返回值返回,或者作为序列中的项使用。
所有 Numba JIT 编译的函数和 Numba cfunc
编译的函数都支持一流函数,除非:
使用非CPU编译器,
编译后的函数是一个 Python 生成器。
编译后的函数省略了参数,
或者编译后的函数返回可选值。
要禁用一级函数支持,请使用 no_cfunc_wrapper=True
装饰器选项。
例如,考虑一个例子,其中 Numba JIT 编译的函数将用户指定的函数作为组合应用于输入参数:
>>> @numba.njit
... def composition(funcs, x):
... r = x
... for f in funcs[::-1]:
... r = f(r)
... return r
...
>>> @numba.cfunc("double(double)")
... def a(x):
... return x + 1.0
...
>>> @numba.njit
... def b(x):
... return x * x
...
>>> composition((a, b), 0.5), 0.5 ** 2 + 1
(1.25, 1.25)
>>> composition((b, a, b, b, a), 0.5), b(a(b(b(a(0.5)))))
(36.75390625, 36.75390625)
在这里,cfunc
编译的函数 a
和 b
被视为一等函数对象,因为这些函数作为参数传递给了 Numba JIT 编译的函数 composition
,也就是说,composition
是独立于其参数函数对象(这些对象收集在输入参数 funcs
中)进行 JIT 编译的。
目前,一级函数对象可以是 Numba cfunc
编译的函数、JIT 编译的函数,以及实现了包装地址协议(WAP,见下文)的对象,但有以下限制:
上下文 |
JIT 编译 |
cfunc 编译 |
WAP 对象 |
---|---|---|---|
可以用作参数 |
是的 |
是的 |
是的 |
可以被调用 |
是的 |
是的 |
是的 |
可以用作项目 |
是* |
是的 |
是的 |
可以返回 |
是的 |
是的 |
是的 |
命名空间范围 |
是的 |
是的 |
是的 |
自动重载 |
是的 |
不 |
不 |
* 在一系列一级函数对象中,至少有一个对象必须具有精确的类型。
封装地址协议 - WAP
Wrapper Address Protocol 提供了一个 API,使得任何 Python 对象成为 Numba JIT 编译函数的头等函数。这假设 Python 对象表示一个可以通过其内存地址(函数指针值)从 Numba JIT 编译函数调用的编译函数。所谓的 WAP 对象必须定义以下两种方法:
此外,WAP 对象可能实现 __call__
方法。当从 Numba JIT 编译函数中以 对象模式 调用 WAP 对象时,这是必要的。
作为一个例子,让我们在一个 Numba JIT 编译的函数中调用标准数学库函数 cos
。在加载数学库并使用 ctypes
包后,可以确定 cos
的内存地址:
>>> import numba, ctypes, ctypes.util, math
>>> libm = ctypes.cdll.LoadLibrary(ctypes.util.find_library('m'))
>>> class LibMCos(numba.types.WrapperAddressProtocol):
... def __wrapper_address__(self):
... return ctypes.cast(libm.cos, ctypes.c_voidp).value
... def signature(self):
... return numba.float64(numba.float64)
...
>>> @numba.njit
... def foo(f, x):
... return f(x)
...
>>> foo(LibMCos(), 0.0)
1.0
>>> foo(LibMCos(), 0.5), math.cos(0.5)
(0.8775825618903728, 0.8775825618903728)
杂项类型
有一些非数值类型不属于其他类别。
类型名称 |
注释 |
---|---|
pyobject |
通用的 Python 对象 |
voidptr |
原始指针,不能对其执行任何操作 |
高级类型
对于更高级的声明,您必须显式调用 Numba 提供的辅助函数或类。
警告
这里记录的API不保证是稳定的。除非必要,建议使用 无签名变体的@jit 让Numba推断参数类型。
推理
- numba.typeof(value)
创建一个准确描述给定 Python 值 的 Numba 类型。如果该值在 nopython 模式 下不受支持,则会引发
ValueError
。>>> numba.typeof(np.empty(3)) array(float64, 1d, C) >>> numba.typeof((1, 2.0)) (int64, float64) >>> numba.typeof([0]) reflected list(int64)
NumPy 标量
除了使用 typeof()
,非平凡的标量(如结构化类型)也可以通过编程方式构造。
- numba.from_dtype(dtype)
创建一个与给定 NumPy dtype 对应的 Numba 类型:
>>> struct_dtype = np.dtype([('row', np.float64), ('col', np.float64)]) >>> ty = numba.from_dtype(struct_dtype) >>> ty Record([('row', '<f8'), ('col', '<f8')]) >>> ty[:, :] unaligned array(Record([('row', '<f8'), ('col', '<f8')]), 2d, A)
- class numba.types.NPDatetime(unit)
为给定的 unit 创建一个 Numba 类型的 NumPy 日期时间。unit 应为 NumPy 识别的代码之一(例如
Y
、M
、D
等)。
数组
- class numba.types.Array(dtype, ndim, layout)
创建一个数组类型。dtype 应为 Numba 类型。ndim 是数组的维数(一个正整数)。layout 是一个字符串,表示数组的布局:
A
表示任意布局,C
表示 C 连续,F
表示 Fortran 连续。
可选类型
- class numba.optional(typ)
基于底层 Numba 类型 typ 创建一个可选类型。可选类型将允许 typ 类型的任何值或
None
。>>> @jit((optional(intp),)) ... def f(x): ... return x is not None ... >>> f(0) True >>> f(None) False
类型注解
- numba.extending.as_numba_type(py_type)
创建一个对应于给定 Python 类型注解 的 Numba 类型。如果类型注解无法映射到 Numba 类型,则会引发
TypingError
。此函数旨在在静态编译时用于评估 Python 类型注解。对于 Python 对象的运行时检查,请参见上面的typeof
。对于任何 numba 类型,
as_numba_type(nb_type) == nb_type
。>>> numba.extending.as_numba_type(int) int64 >>> import typing # the Python library, not the Numba one >>> numba.extending.as_numba_type(typing.List[float]) ListType[float64] >>> numba.extending.as_numba_type(numba.int32) int32
as_numba_type
会自动更新以包含任何@jitclass
。>>> @jitclass ... class Counter: ... x: int ... ... def __init__(self): ... self.x = 0 ... ... def inc(self): ... old_val = self.x ... self.x += 1 ... return old_val ... >>> numba.extending.as_numba_type(Counter) instance.jitclass.Counter#11bad4278<x:int64>
目前
as_numba_type
仅用于推断@jitclass
的字段。