融合类型(模板)¶
注意
此页面使用两种不同的语法变体
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))
ctypedef fused char_or_float:
char
double
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))
这将给出
>>> 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)
ctypedef fused my_fused_type:
int
double
这将声明一个名为 my_fused_type
的新类型,它可以是 int
或 double
。
只有名称可以用于组成类型,但它们可以是任何(非融合)类型,包括 typedef。即,可以写
my_double = cython.typedef(cython.double)
my_fused_type = cython.fused_type(cython.int, my_double)
ctypedef double my_double
ctypedef fused fused_type:
int
my_double
使用融合类型¶
融合类型可用于声明函数或方法的参数
@cython.cfunc
def cfunc(arg: my_fused_type):
return arg + 1
cdef cfunc(my_fused_type arg):
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
cdef cfunc(my_fused_type arg1, my_fused_type arg2):
# arg1 and arg2 always have the same type here
return arg1 + arg2
在这里,两个参数的类型都是 int 或 double(根据前面的示例),因为它们使用相同的融合类型名称 my_fused_type
。在参数中混合不同的融合类型(或不同名称的融合类型)将独立地特化它们
def func(x: A, y: B):
...
def func(A x, B y):
...
这将导致针对 A
和 B
中包含的所有类型组合的专用代码路径,例如
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
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 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)):
...
cdef myfunc(A[:, :] x):
...
# and
cdef otherfunc(A *x):
...
以下代码片段展示了使用指向融合类型的指针的示例
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))
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)
注意
在 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
...
选择专业化¶
您可以通过两种方式选择专业化(具有特定或专门化(即非融合)参数类型的函数实例):通过索引或通过调用。
索引¶
您可以使用类型对函数进行索引以获取某些专业化,即
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)
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 中调用
>>> 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)
cdef myfunc(A *x):
...
# Specialize using int, not int *
myfunc[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)
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)
对于使用例如 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"
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"
对于从 Cython 调用的 cdef
或 cpdef
函数,这意味着专业化是在编译时确定的。对于 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)
融合的 cdef
和 cpdef
函数可以像下面这样强制转换为 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)
类型检查专业化¶
可以根据融合参数的专业化做出决定。错误的条件会被修剪以避免无效代码。可以使用 is
、is not
和 ==
和 !=
来查看融合类型是否等于某个其他非融合类型(以检查专业化),或者使用 in
和 not 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"
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"
条件 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
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__¶
最后,来自 def
或 cpdef
函数的函数对象具有一个属性 __signatures__
,它将签名字符串映射到实际的专用函数。这可能对检查很有用。
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)
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)
>>> 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
但是,更好的索引方法是提供类型列表,如 索引 部分所述。