类型和签名

理由

作为一个优化编译器,Numba 需要确定每个变量的类型以生成高效的机器代码。Python 的标准类型对于这一点来说不够精确,因此我们不得不开发自己的细粒度类型系统。

当你试图检查Numba的类型推断结果时,主要会遇到Numba类型,用于 调试教育 目的。然而,如果你在 提前编译 代码时,需要明确使用类型。

签名

签名指定了一个函数的类型。具体允许哪种类型的签名取决于上下文(AOTJIT 编译),但签名总是涉及一些 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 编译的函数 ab 被视为一等函数对象,因为这些函数作为参数传递给了 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 对象必须定义以下两种方法:

__wrapper_address__(self) int

返回一级函数的内存地址。当 Numba JIT 编译的函数尝试调用给定的 WAP 实例时,使用此方法。

signature(self) numba.typing.Signature

返回给定的一级函数的签名。当将给定的 WAP 实例传递给 Numba JIT 编译函数时,使用此方法。

此外,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 识别的代码之一(例如 YMD 等)。

class numba.types.NPTimedelta(unit)

为给定 unit 的 NumPy timedeltas 创建一个 Numba 类型。unit 应为 NumPy 识别的代码之一(例如 YMD 等)。

参见

NumPy 日期时间单位.

数组

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 的字段。