Cython 中的类型系统

由于最近的几个项目都有接触到 Cython 的使用,也积累了一些 Cython 的经验,于是决定把上次的介绍续上(蜜汁发现刚好过了一年。。),将 Cython 的一些用法记录下来。这一些文章可以作为一些学习的参考,不过 kick-start 的话还是去看看之前的博文以及官方的例子吧~

Cython 对 C/C++ 内置类型的支持

Cython 在类型设置上和 C/C++ 是十分相似的,不仅默认支持基本 C 类型 intfloatunsigned long 等等以及他们的指针类型,还支持 C 的 structunionenum 以及 C++ 的 cppclass(即 C++ 中的类)。需要注意的是在 Cython 中,定义 C/C++ 对象都需要使用 cdef 关键字,或者使用 cpdef 关键字定义一个 Python 封装过的对象。另外 Cython 还通过 ctypedef 支持 C/C++ 形式的 typedef。下面是一些定义变量的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cdef int i, j, k # i,j,k是C中的int类型
cdef float f, g[42], *h

cdef struct Grail: # C形式的结构体
int age
float volume
cdef Grail *gp # 结构体指针,注意不是cdef struct Grail

cdef enum CheeseState: # C++形式的枚举类
hard = 1
soft = 2
runny = 3

cdef enum: # C形式的匿名枚举
tons_of_spam = 3
  • size_tPy_ssize_t 也是受 Cython 语言支持的类型。如果想使用 uint32_t 形式的类型,则需要添加 from libc.stdint cimport uint32_t 形式的语句。
  • 这些变量的作用域定义与 Python 是相同的,但是 cdef 定义的对象是不能 import 到 Python 代码中的,而 cpdef 定义的对象则可以。

Cython 对 Python 内置类型的支持

尽管 Cython 兼容 Python 的语法,但是我们还可以通过 cdef 来使得 Python 对象具有静态类型,以提高运行效率。Cython 在除了支持 Python 内置的 listdicttuple 外,还支持直接声明 Python 的基本类型,这需要通过 from cpython cimport int 形式的语句来实现。此外有一类特殊的类型是 ctuple,能够直接定义 tuple 中元素的类型与数量,有些类似于 C# 7 中的语法,例子如下:

1
2
3
4
5
6
7
cdef dict sd # 直接定义内置类型,实际是PyDict对象

from cpython cimport int as pyint
cdef pyint big_a # PyInt对象

from libcpp cimport bool
cdef (int, unsigned long, bool) table # ctuple对象

Cython 的类型映射

在将 Python 的基本类型对象与 C/C++ 的基本类型对象进行相互赋值的过程中,Cython 会进行自动的类型转换,可识别的转换规则有:

C typesFrom Python typesTo Python types
[unsigned] char, [unsigned] short, int, longint, longint
unsigned int, unsigned long, [unsigned] long longint, longlong
float, double, long doubleint, long, floatfloat
char*[1]str/bytesstr/bytes
C array[2]iterablelist
structuniondict

[1] Python2 中转换成 str,Python3 中转换成 bytes [2] char 数组除外

如果自动类型转换不被支持、或者自动转换类型不是所需类型的话,还可以使用强制类型转换,语法是在变量前加 <type-name>,例如

1
2
3
4
5
6
7
8
9
from cpython.ref cimport PyObject

cdef extern from *:
ctypedef Py_ssize_t Py_intptr_t

python_string = "foo"

cdef void* ptr = <void*>python_string
cdef Py_intptr_t adress_in_c = <Py_intptr_t>ptr

如果使用 <type-name?> 形式则会在转换时执行运行时检查

Cython 中的模板

Cython 还支持 C++ 中的模板,语法是 class_name[template_args],不过模板参数目前只支持类型参数,因此更像是 C# 中的泛型。对模板的支持不仅是可以声明模板类,还可以支持绑定已有 C++ 的模板类,这也是 Cython 区别于 Boost.Python、Pybind11 等 C++ 端绑定库的重要一点。由于后者在编译期无法了解 Python 代码的使用需求,因此只能在编译器展开模板(Instantiation),而 Cython 则可以通过.pxd 头文件保留模板的格式,在引用该库需要编译时再展开。

模板的应用例子如下:

1
2
3
4
5
6
7
from libcpp.vector cimport vector

cdef vector[int] vect
cdef int i, x

for i in range(10):
vect.push_back(i)

参考内容:
Cython 文档
Cython 文档 - Language Basics