即使您阅读了官方文档,该文档也会从您的耳朵和耳朵中溜走,所以我想在这里进行总结。
def,cdef,cpdef
老实说,当我第一次看到它时,我想知道如何正确使用它,但是可以控制范围,可以这么说:
<表格>
tr>
header>
<身体>
tr>
tr>
tbody>
table>
定义为def
的Python函数
首先,
cdef
的C函数定义
如果要定义可以从C语言端调用的函数,请使用
由于它是
,因此
- 所有变量的类型说明(基于C语言)
-
所有函数调用均等效于
cdef (cdef 或C库函数)
使用cdef
进行变量定义
如果要使用C语言类型定义变量,请基本上使用
在
Python函数中用
另一个优点是,例如,如果您事先知道Python变量
使用cpdef
的混合函数定义
到目前为止,我们可以看到
cdef类别
像
函数一样,您可以在类定义中使用
- 从Python方面,您可以像创建常规类一样创建实例。
-
如果在
.pyx 文件中写入def ,则实例方法基本上等效于cdef 。 -
实例方法也可以从Python端称为
built-in function 。 -
方法和实例变量基本上都只能在Cython上定义。实例变量的初始化和释放由功能
__cinit__ 和__dealloc__ (不太可能调用__init__ 和__del__ )完成。-
通过
cpdef 方法,它变成了可以在Python端重写的方法(但是会慢一些)。 -
通过声明实例变量
public ,Python会将其视为只读变量。 -
如果要读取或写入实例变量,请使其成为属性。使用
@property 或@xxx.setter 装饰器限定方法。此方法不必是cpdef ,对于def 似乎很好。
-
通过
调用C语言库
为了使用外部库,有必要以Cython可以理解的方式描述头文件中定义的内容(函数原型,结构等)。大致有两种方法,但是两种方法的符号相同。
如何在.pyx 文件中写入
这是更简单的方法。作为图像,感觉像是"将库的符号直接导入到
对于标准库,它看起来像这样:
1 2 3 4 5 6 | cdef extern from "<math.h>": # 標準ライブラリの場合 DEF HUGE_VAL = 1e500 # #defineマクロ ('='に注意) double exp(double x) # 関数プロトタイプ # 使う関数、マクロだけ定義しておけばよい # この状態で、同じ.pxdファイル内で HUGE_VALやexpを使うことができる |
对于其他(普通)库:
1 2 3 4 5 6 7 8 9 10 11 12 13 | cdef extern from "spam.h": DEF ARTIFICIAL = 1 # #defineマクロ ctypedef int fattype_t # typedef int spam_counter # グローバル変数 void order_spam(int tons) # 関数プロトタイプ struct spam: # 構造体定義 fattype_t fat_type double fat_content # アクセスする必要があるメンバだけ定義しておけばよい |
如何声明到.pxd 文件
Cython允许您创建一个扩展名为
更像是在模块化名称空间中定义C库符号。这样可以方便地以
例如,如下定义
1 2 3 4 5 | # in: libspam.pxd cdef extern from "spam.h": void order_spam(int tons) struct spam: ... |
这样,可以从同一目录中的
1 2 3 4 5 6 | # in: spamconsumer.pyx cimport libspam # libspam.pxdの読み込み libspam.order_spam(20) # spam.hの関数order_spamの呼び出し cdef libspam.spam *STOCK # spam.hの構造体spamの参照 |
对于由各种
注意1:关于宏
似乎需要使用可由Cython解释的表达式来定义
宏。因此,在某些情况下,您必须引用头文件并复制立即值...可以将宏定义函数表示为原型吗?还是应该定义
注2:关于结构
您只需要定义要访问的成员,因此,如果您有一个将用作黑盒的结构,则可以将其声明为
只要在相应的头文件中定义了符号,如果在
定义
注3:构建参数
的描述
对于不是
标准库的库,除非明确为
下面是Cython文档中的模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from setuptools import Extension, setup from Cython.Build import cythonize extensions = [ # 第1引数がモジュール名、 # 第2引数がモジュールに必要なコードファイル(.pyx, .c, ...)の配列 Extension("primes", ["primes.pyx"], include_dirs=[...], # インクルードディレクトリの配列 libraries=[...], # ライブラリ名の配列(gccの'-l'オプションに渡す名前) library_dirs=[...]),# ライブラリディレクトリの配列(gccの'-L'オプション) # Everything but primes.pyx is included here. Extension("*", ["*.pyx"], include_dirs=[...], libraries=[...], library_dirs=[...]), ] setup( name="My hello app", ext_modules=cythonize(extensions), ) |
实际上,
正如我在其他地方所写的,您可以使用
删除Python的所有原始检查机制
Python具有多种检查机制,这些机制赋予了Python灵活性。但是,在追求处理速度时这可能是个问题。
类型检查
在Python中,似乎没有类型,可以说对象的属性访问仅由
通过在定义
函数时指定参数类型,可以保证它是与
1 2 3 4 5 | def pydef_add(int a, int b): return a + b cdef int cdef_add(int a, int b): return a + b |
例如,在
1 2 3 4 5 6 | cimport numpy as cnumpy ... cdef cnumpy.ndarray arr # 配列ポインタの定義 cdef cnumpy.float64_t value = 0.0 # numpy.float64型の変数の定義 |
不检查
每当调用名称指向
除此之外,还有一个修饰符
1 2 | def complicated(cnumpy.ndarray array not None): pass |
阵列检查
Python在访问数组时自动检查下标是否溢出。如果您的代码保证"没有上溢或下溢",则可以使用
另外,当使用NumPy数组时,您可以预先声明一些关于数组形状的声明,这样可以简化过程(Cython会在编译时执行):
1 2 | def calc_max(cnumpy.ndarray[cnumpy.float64_t, ndim=1] vec): ... |
如上例所示,您可以指定数组中使用的数据类型和数据的维数。通过在此处添加上面的
当心GIL
对GIL
的感官理解
Python具有一种称为全局解释器锁(GIL)的机制,我认为这仅是Python速度缓慢的一半原因。这是对以下原则的要求:
无论您有多少个Python线程,一个Python进程中只有一个Python解释器。
从某种意义上讲,这是用于维护进程内Python环境唯一性的重要机制。因此,为防止许多线程在访问一个解释器时陷入混乱,只有运行中的线程会获得解释器的锁,而其他线程正在等待该锁。这是GIL。
减少Python环境
中的处理
这就是为什么当您执行Python代码时,一定要获得GIL(甚至从Cython代码中获得)。即使您正在使用本机线程,此处也会发生GIL冲突,因此处理速度肯定会放慢。
由于它是
,因此重要的是"尽可能多地替换C本机处理(仅使用
不要迷失在GIL中(请注意回调函数)
换句话说,上面的观点是,每当使用Python对象时都需要一个GIL。实际上,如果要在回调函数中使用Python对象,请小心。
一些与硬件相关的回调函数可能不是Python进程产生的线程。然后,在回调环境中不知道解释器的地址,并且当我尝试在其中使用Python对象时,程序崩溃,并显示"找不到GIL!"错误。
在这种情况下,您似乎必须启动一个可以从Python看到的线程(无论是Python线程还是PyQt线程),并等待使用
结论
Cython很方便,但是很难理解幕后发生的事情的编程模型。仍然有许多功能(