前面我们介绍了静态类,静态类实例在属性查找方面比动态类要高效很多,因为静态类实例的属性是通过数组存储的,一个萝卜一个坑,访问的时候基于索引访问。但无论是静态类还是动态类,其实例在查找属性时,底层都会调用 PyObject_GetAttr 这个 C API。
那么问题来了,能不能再快一点呢?因为一旦涉及到 Python/C API,效率都是不高的,而 Python 的对象在底层都是一个 C 结构体,那么在查找属性的时候能不能直接访问 C 结构体的字段呢?不要再走 Python 的 C API 了。
我们举例说明:
cdef class Score: cdef public: int chinese, math, english def __init__(self, int chinese, int math, int english): self.chinese = chinese self.math = math self.english = english s = Score(90, 98, 92) print( s.chinese, s.math, s.english ) # 90 98 92
以上是一段 Cython 代码,如果换成功能相同的 C 代码的话:
#include <stdio.h> typedef struct { int chinese; int math; int english; } Score; int main() { Score s = {90, 98, 92}; printf("%d %d %d\n", s.chinese, s.math, s.english); // 90 98 92 }
那么问题来了,我们能不能将 Cython 中的属性访问,转成 C 一级的属性访问呢?答案是可以的,下面来看一下具体的做法。
# 文件名:score.pyx cdef class Score: cdef public: int chinese, math, english def __init__(self, int chinese, int math, int english): self.chinese = chinese self.math = math self.english = english
我们需要先将 score.pyx 编译成扩展模块,编译方式很简单,这里不再赘述了。然后还要编写一个头文件 score.h:
#include <Python.h> // 定义一个 C 结构体,模拟扩展类 Score typedef struct { PyObject_HEAD int chinese; int math; int english; } C_Score;
接下来再编写一个 Cython 源文件导入它:
# 文件名:cython_test.pyx cdef extern from "score.h": ctypedef class score.Score [object C_Score]: cdef: int chinese int math int english # 这里我们使用了 ctypedef class,可以简单认为导入了一个类 # 而 ctypedef class 紧跟的是 score.Score,编译器在看到之后 # 就知道要从 score 模块里导入类 Score # 所以我们不需要 from score import Score,会自动导入 # 但是导入了还不算完,后面还跟了一个 C_Score # C_Score 和 score.Score 里面的成员都是一样的 # 然后 Cython 编译器会生成 C 结构体字段的直接访问 # 而不会再走 Python 的 C API def summer(Score s): # 进行类型声明的话,只需要使用 Score 即可 # 并且这里的 Score 只能在 Cython 内部使用 return s.chinese + s.math + s.english
然后进行编译,导入测试一下:
from score import Score import cython_test s = Score(99, 98, 97) print(cython_test.summer(s)) # 294
结果没有任何问题,并且此时 Cython 是直接访问的结构体字段,而不是使用 __getattr__。
然后要说明的是,类的名字和 C 结构体的名字不要求相同,但是内部字段的名字应该是相同的。假设 C_Score 的字段如下:
#include <Python.h> // 定义一个 C 结构体,模拟扩展类 Score typedef struct { PyObject_HEAD int a; int b; int c; } C_Score;
那么声明的时候就应该这么做:
# 文件名:cython_test.pyx cdef extern from "score.h": ctypedef class score.Score [object C_Score]: cdef: int chinese "a" int math "b" int english "c"
否则的话,在 C 结构体中就找不到对应的字段。当然啦,找不到的话会退化使用 __getattr__,不会报错。
此外该方法也适用于内置类型,不过用的不多,而且我们也很少使用 ctypedef class,因此本文的内容了解一下即可。