融合类型(模板)

注意

此页面使用两种不同的语法变体

  • Cython 特定的 cdef 语法,旨在使类型声明简洁,并易于从 C/C++ 的角度阅读。

  • 纯 Python 语法,允许在 纯 Python 代码 中使用静态 Cython 类型声明,遵循 PEP-484 类型提示和 PEP 526 变量注释。

    要在 Python 语法中使用 C 数据类型,您需要在要编译的 Python 模块中导入特殊的 cython 模块,例如

    import cython
    

    如果您使用纯 Python 语法,我们强烈建议您使用最新的 Cython 3 版本,因为与 0.29.x 版本相比,这里已经进行了重大改进。

融合类型允许您拥有一个可以引用多种类型的类型定义。这使您可以编写一个可以在多种类型的值上运行的单一静态类型化 Cython 算法。因此,融合类型允许 泛型编程,类似于 C++ 中的模板或 Java/C# 等语言中的泛型。

注意

融合类型目前不支持作为扩展类型的属性。只有变量和函数/方法参数可以使用融合类型声明。

快速入门

char_or_float = cython.fused_type(cython.char, cython.double)



@cython.ccall
def plus_one(var: char_or_float) -> char_or_float:
    return var + 1


def show_me():

    a: cython.char = 127
    b: cython.double = 127
    print('char', plus_one(a))
    print('float', plus_one(b))

这将给出

>>> show_me()
char -128
float 128.0

plus_one(a) 将融合类型 char_or_float 特化为 char,而 plus_one(b)char_or_float 特化为 float

声明融合类型

融合类型可以按如下方式声明

my_fused_type = cython.fused_type(cython.int, cython.float)

这将声明一个名为 my_fused_type 的新类型,它可以是 intdouble

只有名称可以用于组成类型,但它们可以是任何(非融合)类型,包括 typedef。即,可以写

my_double = cython.typedef(cython.double)
my_fused_type = cython.fused_type(cython.int, my_double)

使用融合类型

融合类型可用于声明函数或方法的参数

@cython.cfunc
def cfunc(arg: my_fused_type):
    return arg + 1

如果相同的融合类型在函数参数中出现多次,那么它们将具有相同的特化类型

@cython.cfunc
def cfunc(arg1: my_fused_type, arg2: my_fused_type):
    # arg1 and arg2 always have the same type here
    return arg1 + arg2

在这里,两个参数的类型都是 int 或 double(根据前面的示例),因为它们使用相同的融合类型名称 my_fused_type。在参数中混合不同的融合类型(或不同名称的融合类型)将独立地特化它们

def func(x: A, y: B):
    ...

这将导致针对 AB 中包含的所有类型组合的专用代码路径,例如

my_fused_type = cython.fused_type(cython.int, cython.double)



my_fused_type2 = cython.fused_type(cython.int, cython.double)


@cython.cfunc
def func(a: my_fused_type, b: my_fused_type2):
    # a and b may have the same or different types here
    print("SAME!" if my_fused_type is my_fused_type2 else "NOT SAME!")
    return a + b

注意

目前,简单的 typedef 来重命名融合类型在这里不起作用。请参见 Github 问题 #4302

融合类型和数组

请注意,只有数值类型的特化可能不是很有用,因为通常可以依赖于类型的提升。但是,对于数组、指针和内存的类型化视图来说,情况并非如此。实际上,可以写

@cython.cfunc
def myfunc(x: A[:, :]):
    ...

# and

@cython.cfunc
cdef otherfunc(x: cython.pointer(A)):
    ...

以下代码片段展示了使用指向融合类型的指针的示例

my_fused_type = cython.fused_type(cython.int, cython.float)


@cython.cfunc
def func(a: cython.pointer(my_fused_type)):
    print(a[0])

def main():
    a: cython.int = 3
    b: cython.float = 5.0

    func(cython.address(a))
    func(cython.address(b))

注意

在 Cython 0.20.x 及更早版本中,编译器在类型签名中多个内存视图使用融合类型时,会生成所有类型组合的完整交叉积,例如:

def myfunc(A[:] a, A[:] b):
    # a and b had independent item types in Cython 0.20.x and earlier.
    ...

这对大多数用户来说是意料之外的,不太可能被需要,并且与其他结构化类型声明(如融合类型的 C 数组)不一致,这些数组被认为是相同的类型。因此,在 Cython 0.21 中进行了更改,以对融合类型的所有内存视图使用相同的类型。为了获得原始行为,只需在不同的名称下声明相同的融合类型,然后在声明中使用它们即可。

ctypedef fused A:
    int
    long

ctypedef fused B:
    int
    long

def myfunc(A[:] a, B[:] b):
    # a and b are independent types here and may have different item types
    ...

为了在旧版本的 Cython(0.21 之前)中也只获得相同的类型,可以使用 ctypedef

ctypedef A[:] A_1d

def myfunc(A_1d a, A_1d b):
    # a and b have identical item types here, also in older Cython versions
    ...

选择专业化

您可以通过两种方式选择专业化(具有特定或专门化(即非融合)参数类型的函数实例):通过索引或通过调用。

索引

您可以使用类型对函数进行索引以获取某些专业化,即

indexing.py
import cython

fused_type1 = cython.fused_type(cython.double, cython.float)



fused_type2 = cython.fused_type(cython.double, cython.float)


@cython.cfunc
def cfunc(arg1: fused_type1, arg2: fused_type1):
    print("cfunc called:", cython.typeof(arg1), arg1, cython.typeof(arg2), arg2)

@cython.ccall
def cpfunc(a: fused_type1, b: fused_type2):
    print("cpfunc called:", cython.typeof(a), a, cython.typeof(b), b)

def func(a: fused_type1, b: fused_type2):
    print("func called:", cython.typeof(a), a, cython.typeof(b), b)

# called from Cython space
cfunc[cython.double](5.0, 1.0)
cpfunc[cython.float, cython.double](1.0, 2.0)
# Indexing def functions in Cython code requires string names
func["float", "double"](1.0, 2.0)

索引函数可以直接从 Python 中调用

>>> import cython
>>> import indexing
cfunc called: double 5.0 double 1.0
cpfunc called: float 1.0 double 2.0
func called: float 1.0 double 2.0
>>> indexing.cpfunc[cython.float, cython.float](1, 2)
cpfunc called: float 1.0 float 2.0
>>> indexing.func[cython.float, cython.float](1, 2)
func called: float 1.0 float 2.0

如果融合类型用作更复杂类型的组件(例如指向融合类型的指针,或融合类型的内存视图),那么您应该使用单个组件对函数进行索引,而不是使用完整的参数类型

@cython.cfunc
def myfunc(x: cython.pointer(A)):
    ...

# Specialize using int, not int *
myfunc[cython.int](myint)

对于来自 Python 空间的内存视图索引,我们可以执行以下操作

my_fused_type = cython.fused_type(cython.int[:, ::1], cython.float[:, ::1])

def func(array: my_fused_type):
    print("func called:", cython.typeof(array))

my_fused_type[cython.int[:, ::1]](myarray)

对于使用例如 cython.numeric[:, :] 也是如此。

调用

融合函数也可以使用参数调用,其中调度会自动确定

def main():
    p1: cython.double = 1.0
    p2: cython.float = 2.0
    cfunc(p1, p1)          # prints "cfunc called: double 1.0 double 1.0"
    cpfunc(p1, p2)         # prints "cpfunc called: double 1.0 float 2.0"

对于从 Cython 调用的 cdefcpdef 函数,这意味着专业化是在编译时确定的。对于 def 函数,参数在运行时进行类型检查,并执行尽力而为的方法来确定需要哪个专业化。这意味着这可能会导致运行时 TypeError,如果未找到专业化。如果函数的类型未知(例如,如果它是外部的并且没有 cimport),则 cpdef 函数与 def 函数的处理方式相同。

自动调度规则通常按以下顺序进行,优先级从高到低

  • 尝试找到完全匹配

  • 选择最大的对应数值类型(最大的浮点数,最大的复数,最大的整数)

内置融合类型

为了方便起见,提供了一些内置的融合类型,它们是

cython.integral # short, int, long
cython.floating # float, double
cython.numeric  # short, int, long, float, double, float complex, double complex

强制转换融合函数

注意

纯 Python 模式目前不支持指向函数的指针。(GitHub 问题 #4279

融合的 cdefcpdef 函数可以像下面这样强制转换为 C 函数指针或分配给 C 函数指针

cdef myfunc(cython.floating, cython.integral):
    ...

# assign directly
cdef object (*funcp)(float, int)
funcp = myfunc
funcp(f, i)

# alternatively, cast it
(<object (*)(float, int)> myfunc)(f, i)

# This is also valid
funcp = myfunc[float, int]
funcp(f, i)

类型检查专业化

可以根据融合参数的专业化做出决定。错误的条件会被修剪以避免无效代码。可以使用 isis not==!= 来查看融合类型是否等于某个其他非融合类型(以检查专业化),或者使用 innot in 来确定专业化是否是另一组类型(指定为融合类型)的一部分。例如

bunch_of_types = cython.fused_type(bytes, cython.int, cython.float)






string_t = cython.fused_type(cython.p_char, bytes, unicode)



@cython.cfunc
def myfunc(i: cython.integral, s: bunch_of_types) -> cython.integral:
    # Only one of these branches will be compiled for each specialization!
    if cython.integral is int:
        print('i is an int')
    elif cython.integral is long:
        print('i is a long')
    else:
        print('i is a short')

    if bunch_of_types in string_t:
        print("s is a string!")
    return i * 2

myfunc(cython.cast(cython.int, 5), b'm')  # will print "i is an int" and "s is a string"
myfunc(cython.cast(cython.long, 5), 3)    # will print "i is a long"
myfunc(cython.cast(cython.short, 5), 3)   # will print "i is a short"

条件 GIL 获取/释放

获取和释放 GIL 可以由编译时已知的条件控制(参见 条件获取/释放 GIL)。

当与融合类型结合使用时,这将非常有用。融合类型函数可能需要处理 Cython 本地类型(例如 cython.int 或 cython.double)和 Python 类型(例如 object 或 bytes)。条件获取/释放 GIL 提供了一种方法,可以在释放 GIL(对于 Cython 本地类型)和持有 GIL(对于 Python 类型)的情况下运行相同的代码段。

import cython

double_or_object = cython.fused_type(cython.double, object)

def increment(x: double_or_object):
    with cython.nogil(double_or_object is not object):
        # Same code handles both cython.double (GIL is released)
        # and python object (GIL is not released).
        x = x + 1
    return x

increment(5.0)  # GIL is released during increment
increment(5)    # GIL is acquired during increment

__signatures__

最后,来自 defcpdef 函数的函数对象具有一个属性 __signatures__,它将签名字符串映射到实际的专用函数。这可能对检查很有用。

indexing.py
import cython

fused_type1 = cython.fused_type(cython.double, cython.float)



fused_type2 = cython.fused_type(cython.double, cython.float)


@cython.ccall
def cpfunc(a: fused_type1, b: fused_type2):
    print("cpfunc called:", cython.typeof(a), a, cython.typeof(b), b)
>>> from indexing import cpfunc
>>> cpfunc.__signatures__,
({'double|double': <cyfunction __pyx_fuse_0_0cpfunc at 0x107292f20>, 'double|float': <cyfunction __pyx_fuse_0_1cpfunc at 0x1072a6040>, 'float|double': <cyfunction __pyx_fuse_1_0cpfunc at 0x1072a6120>, 'float|float': <cyfunction __pyx_fuse_1_1cpfunc at 0x1072a6200>},)

列出的签名字符串也可以用作融合函数的索引,但索引格式可能会在 Cython 版本之间发生变化。

>>> specialized_function = cpfunc["double|float"]
>>> specialized_function(5.0, 1.0)
cpfunc called: double 5.0 float 1.0

但是,更好的索引方法是提供类型列表,如 索引 部分所述。