Skip to content

融合类型(模板)

原文: http://docs.cython.org/en/latest/src/userguide/fusedtypes.html

融合类型允许您有一个可以引用多种类型的类型定义。这允许您编写一个静态类型的 cython 算法,该算法可以对多种类型的值进行操作。因此,融合类型允许泛型编程,类似于 C ++中的模板或 Java / C#等语言中的泛型。

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

快速入门

# 经典风格
from __future__ import print_function

ctypedef fused char_or_float:
    char
    float

cpdef char_or_float plus_one(char_or_float var):
    return var + 1

def show_me():
    cdef:
        char a = 127
        float b = 127
    print('char', plus_one(a))
    print('float', plus_one(b))

# 纯Python风格
from __future__ import print_function

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



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


def show_me():

    a: cython.char = 127
    b: cython.float = 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

声明熔断类型

融合类型可以这么声明:

# 经典风格
ctypedef fused my_fused_type:
    cython.int
    cython.double

# 纯Python风格
my_fused_type = cython.fused_type(cython.int, cython.float)

这声明了一个名为 my_fused_type 的新类型,它可以是 int double 声明也可以这么写:

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

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

# 经典风格
ctypedef double my_double
my_fused_type = cython.fused_type(cython.int, my_double)

# 纯Python风格
my_double = cython.typedef(cython.double)
my_fused_type = cython.fused_type(cython.int, my_double)

使用融合类型

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

# 经典风格
cdef cfunc(my_fused_type arg):
    return arg + 1

# 纯Python风格
@cython.cfunc
def cfunc(arg: my_fused_type):
    return arg + 1

如果在参数列表中多次使用相同的融合类型,则融合类型的每个特化必须相同:

# 经典风格
cdef cfunc(my_fused_type arg1, my_fused_type arg2):
    # arg1 and arg2 总是有相同的类型
    return arg1 + arg2

# 纯Python风格
@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 ,所以 arg1arg2 都特化为相同的类型。在参数中混用不同的融合类型(或者只是混用名字不同的融合类型)会令他们分别特化。

# 经典风格
def func(A x, B y):
    ...

# 纯Python风格
def func(x: A, y: B):
    ...

其中 AB 是不同的融合类型。这将为 AB 中包含的所有类型组合生成专门的代码路径。

# 经典风格
ctypedef fused my_fused_type:
    int
    double

ctypedef fused my_fused_type2:
    int
    double

cdef func(my_fused_type a, my_fused_type2 b):
    # a and b 类型可能相同也可能不同
    print("SAME!" if my_fused_type is my_fused_type2 else "NOT SAME!")
    return a + b

# 纯Python风格
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 类型可能相同也可能不同
    print("SAME!" if my_fused_type is my_fused_type2 else "NOT SAME!")
    return a + b

注意:用typedef来简单的重命名融合类型目前还不能起到这样的效果。看 Github Issue #4302

融合类型和数组

请注意,仅数字类型的特化可能不是非常有用,因为通常可以依赖于类型提升。但是,对于数组,指针和类型化的内存视图,情况并非如此。事实上,可能会这么写:

# 经典风格
def myfunc(A[:, :] x):
    ...

# and

cdef otherfunc(A *x):
    ...

# 纯Python风格
@cython.cfunc
def myfunc(x: A[:, :]):
    ...

# and

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

下面的代码片段演示了使用融合类型指针的例子:

# 经典风格
ctypedef fused my_fused_type:
    int
    double

cdef func(my_fused_type *a):
    print(a[0])


cdef int b = 3
cdef double c = 3.0

func(&b)
func(&c)

# 纯Python风格
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 及更早版本中,当类型签名中的多个内存视图使用融合类型时,编译器会生成所有类型组合的完整交叉积。 ```python def myfunc(A[:] a, A[:] b): # a and b had independent item types in Cython 0.20.x and earlier. ...

```

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

```python 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

```python 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.pyx
cimport cython

ctypedef fused fused_type1:
    double
    float

ctypedef fused fused_type2:
    double
    float

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


cpdef cpfunc(fused_type1 a, fused_type2 b):
    print("cpfunc called:", cython.typeof(a), a, cython.typeof(b), b)

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

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

# 纯Python风格
# 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

如果融合类型被用做一个更复杂的类型(比如说一个指针融合类型的指针、一个融合类型的内存视图)的一个组件,那么你应该对函数用单独的组件建索引而不是完整的参数类型。

# 经典风格
cdef myfunc(A *x):
    ...

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

# 纯Python风格
@cython.cfunc
def myfunc(x: cython.pointer(A)):
    ...

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

在Python空间中对内存视图进行索引可以这么做:

# 经典风格
ctypedef fused my_fused_type:
    int[:, ::1]
    float[:, ::1]

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

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

# 内存视图
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():
    cdef double p1 = 1.0
    cdef float p2 = 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"

# 纯Python风格
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 。如果函数的类型未知,则 cpdef 函数的处理方式与 def 函数的处理方式相同(例如,如果它是外部的,并且没有 cimport)。

自动派遣的规则通常如下所示,按优先顺序排列:

  • 试着找到精确匹配
  • 选择最大的相应数值类型(最大浮点数,最大复数,最大 int)

内置熔断类型

为方便起见,有一些内置的融合类型,它们是:

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

转换融合函数

注意:目前在纯Python模式下还不支持函数指针。看Github Issue #4279

融合的 cdefcpdef 函数可以转换或赋值给 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)

对特化进行类型检查

可以基于融合参数的特化结果来做出决定。False条件可以被精简掉去除以避免无效代码。可以用 isis not==!= 以查看融合类型是否等于某个其他非融合类型(检查专业化),或使用 innot in 判断专门化是否是另一组类型(指定为融合类型)的一部分。例如:

# 经典风格
cimport cython

ctypedef fused bunch_of_types:
    bytes
    int
    float

ctypedef fused string_t:
    cython.p_char
    bytes
    unicode

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

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

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

# 纯Python风格
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)。

注意:纯Python模式目前不支持有条件的控制获取释放GIL。看Github Issue #5113

这和融合类型结合使用最有用。融合类型函数可能必须同时处理 cython 原生类型(例如 cython.int 或 cython.double)和 python 类型(例如对象或字节)。有条件地获取/释放 GIL 提供了一种运行相同代码的方法,对于Cython原生类型释放GIL、对于Python类型获取GIL:

cimport cython

ctypedef fused double_or_object:
    double
    object

def increment(double_or_object x):
    with 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 属性,该属性将签名字符串映射到实际特化的函数。这可能对Inspection有用。

# 经典风格
# indexing.pyx
cimport cython

ctypedef fused fused_type1:
    double
    float

ctypedef fused fused_type2:
    double
    float

cpdef cpfunc(fused_type1 a, fused_type2 b):
    print("cpfunc called:", cython.typeof(a), a, cython.typeof(b), b)

# 纯Python风格
# 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

无论如何,索引的最佳方式是像索引这一节做的那样列出类型列表



回到顶部