调用cdef
-function或多或少地相当于跳转到内存中的一个地址-应该从中读取/执行命令。问题是如何提供此地址。在某些情况下,我们需要考虑:
A.内联函数
这些函数的代码是内联的,或者函数的定义在同一个转换单元中,因此链接器在链接时(甚至在编译时甚至是编译器)都知道地址,而无需其他库。
一个示例是仅标头库。
后果:中仅应提供包含路径setup.py
。
B.静态链接
我们需要的定义/功能在另一个翻译单元/库中-跳转的目标地址是在链接时计算的,此后不能再更改。
一个示例是添加到扩展定义中的其他c / cpp文件或静态库。
结果:静态库应添加到setup.py,即库路径和库名称以及包含路径。
C.动态链接
共享库/共享库中提供了必要的功能。要跳转到的地址是在运行时从加载程序中计算得出的,可以在程序启动时通过交换已加载的共享对象来替换。
例如stdlibc (通常由g 自动添加)或libm,它们不会由gcc自动添加到链接器命令中。
结果:动态库应添加到setup.py,即库路径和库名称,可能是r-path + include路径。必须在运行时提供共享的对象/ dll。可以在此SO-post中找到更多(使用动态库)有关Cython / Python的信息。
D.通过指针调用
仅当我们通过函数名称调用函数时,才需要链接器。如果通过函数指针调用它,则不需要链接器/加载器,因为函数的地址是已知的-函数指针中的值。
示例:Cython生成的模块使用此机制来启用对通过pxd-file导出的cdef函数的访问。它创建一个__pyx_capi__功能指针的数据结构(将其存储为模块本身的变量),一旦通过ldopen(或Windows的任何等效方法)加载了so / dll,该指针便由加载器填充。字典中的查找仅在模块加载且函数地址被缓存时才发生一次,因此在运行时的调用几乎没有开销。
我们可以检查它,例如通过
#foo.pyx:
cdef void doit():
print("doit")
#foo.pxd
cdef void doit()
>>> cythonize -3 -i foo.pyx
>>> python -c "import foo; print(foo.__pyx_capi__)"
{'doit': <capsule object "void (void)" at 0x7f7b10bb16c0>}
结果:我们需要导入所需的功能。
Numpy有点复杂,因为它使用A和D的复杂组合以便将符号的解析推迟到运行时,因此在链接时(但在运行时!)不需要共享对象/ dll。
可以直接使用numpy -pxd文件中的某些功能,因为它们是内联的(甚至只是定义了),例如PyArray_NDIM,基本上所有来自的功能ndarraytypes.h。这就是为什么可以毫不费力地使用cython的ndarray的原因。
例如,ndarrayobject.h如果没有np.import_array()在初始化步骤中调用,则无法访问其他功能(基本上是from的所有功能)PyArray_FromAny。为什么?
答案是在报头__multiarray_api.h被包括在ndarrayobject.h,但不能在找到的git存储库,因为它是生成的安装信息,其中的定义期间PyArray_FromAny可以查找:
...
static void **PyArray_API=NULL; //usually...
...
#define PyArray_CheckFromAny \
(*(PyObject * (*)(PyObject *, PyArray_Descr *, int, int, int, PyObject *)) \
PyArray_API[108])
...
PyArray_CheckFromAny不是函数的名称,而是保存在中的函数指针的定义PyArray_API,该指针在NULL首次加载模块时未初始化(即)。顺便说一句,还有一个(私有)函数称为PyArray_CheckFromAny,它实际上是函数指针指向的内容-并且由于公共版本是一个定义,因此链接时不会发生名称冲突…
难题的最后一部分-函数_import_array(或多或少地是后面的工作马np.import_array)是内联函数(情况A),因此仅需要include路径即可使用它。
_import_array使用与Cython类似的方法来__pyx_capi__获取函数指针:调用了该字段_ARRAY_API,可以通过以下方法检查该字段:
>>> import numpy.core._multiarray_umath as macore
>>> macore._ARRAY_API
<capsule object NULL at 0x7f17d85f3810>
有关如何PyArray_API初始化的更多信息,可以在我的SO解答中找到。