预定义的 .pxd 文件

简介: 预定义的 .pxd 文件


libc




之前我们使用了这样一条导入语句:from libc.stdlib cimport malloc,显然这是 Cython 提供的预定义 .pxd 文件,位于 Cython 主目录的 Includes 目录中。

C 的头文件在 Cython 里面是以 .pxd 文件的形式存在的,所以这个目录相当于包含了常用的 C 头文件。当然这些都只是声明,至于具体实现则隐藏在编译器当中,我们看不到罢了。

from libc cimport stdio
stdio.printf(<char *>"name = %s, age = %d\n",
             <char *>"satori", <int> 16)
"""
name = satori, age = 16
"""

在 C 里面 #include <stdio.h> 之后,可以直接使用 printf,但是在 Cython 里面则需要通过 stdio.printf 的形式。这是由 Python 的命名空间决定的,因为 printf 位于 stdio 里面,在属性查找时必须先找到 stdio,然后才能找到 printf。

或者使用 from libc.stdio cimport printf 直接将某个具体的函数导入进来也行,这样就能直接使用 printf 了。当然啦,我们也可以 cimport *,这样就和 C 的 #include 一致了。

另外在导入的时候,记得名字不要冲突:

from libc.math cimport sin
from math import sin
"""
from libc.math cimport sin
from math import sin
                ^
------------------------------------------------------------
cython_test.pyx:2:17: Assignment to non-lvalue 'sin'
"""

显然导入的两个函数重名了,因此 Cython 引发了一个编译错误。而为了修复这一点,我们只需要这么做。

from libc.math cimport sin as c_sin
from math import sin as py_sin
print(c_sin(3.1415926 / 2))
print(py_sin(3.1415926 / 2))
"""
0.9999999999999997
0.9999999999999997
"""

此时就没有任何问题了。

另外导入函数的时候不可以重名,但如果我们导入的是模块的话,那么是可以重名的。

from libc cimport math
import math
print(math.sin(math.pi / 2))
"""
1.0
"""

尽管 import math 是在下面,但是调用的时候会从 C 标准库中进行调用。不过这种做法总归是不好的,我们应该修改一下:

from libc cimport math as c_math
import math as py_math

所以这些预定义的 .pxd 文件就类似于 C 的头文件:

  • 它们都声明了 C 一级的数据结构供外界调用;
  • 它们都允许开发者对功能进行拆分, 分别通过不同的模块实现;
  • 它们都实现了公共的 C 级接口;


至于每个文件里面都可以使用哪些函数,可以点进源码中查看:

关于这部分语法下一篇文章会说,总之可以看到和 C 的标准库是一致的,只不过声明的方式不同,一个是 C 的语法,一个是 Cython 的语法。但我们知道 Cython 代码也是要翻译成 C 代码的,所以 from libc cimport stdlib 最终也会被翻译成 #include <stdlib.h>

并且 Includes 目录下除了 libc 之外,还有其它的包:

其中 libcpp 里面包含了 C++ 标准模板库(STL)容器的声明,如:string, vector, list, map, pair, set 等等。而 cpython 则可以让我们访问 Python/C API,当然还有一个重要的包就是 numpy,Cython 也是支持的。


访问 Python/C API




我们来看看如何通过 Cython 来访问 Python/C API。

from cpython.list cimport (
    PyList_SetItem,
    PyList_GetItem
)
cdef list names = ["古明地觉"]
# names[0] = "古明地恋" 等价于如下
PyList_SetItem(names, 0, "古明地恋")
print(names)
"""
['古明地恋']
"""
# print(names[0]) 等价于如下
# 由于 PyList_GetItem 返回的是 PyObject *
# 我们需要转成 object
# PyObject * 是 C 层面的, object 是 Python 层面的
print(<object>PyList_GetItem(names, 0))
"""
古明地恋
"""
from cpython.object cimport (
    PyObject_RichCompareBool,
    Py_LT, Py_LE, Py_EQ,
    Py_NE, Py_GT, Py_GE
)
# 2 < 1 等价于如下
print(
    PyObject_RichCompareBool(2, 1, Py_LT)
)  # False
# 2 > 1 等价于如下
print(
    PyObject_RichCompareBool(2, 1, Py_GT)
)  # True
from cpython.object cimport (
    PyObject_IsInstance,
    PyObject_IsSubclass
)
# isinstance(123, int) 等价于如下
print(
    PyObject_IsInstance(123, int)
)  # True
# issubclass(int, object) 等价于如下
print(
    PyObject_IsSubclass(int, object)
)  # True

Python 的操作都可以通过 Python/C API 来实现,并且这种方式的速度要稍微快那么一点点。但是很明显会比较麻烦,使用 names[0] 肯定比 PyList_GetItem 这种方式来的直接,如果你还对其它的 API 感兴趣,也可以进入源码中查看。

想查看哪个对象的 API,就直接去对应的文件里面找即可。然后在导入的时候,可以直接通过 cpython 来导入,因为其它 .pxd 文件的内容都被导入到 __init__.pxd 里面了。

这个 __init__.pxd 和 __init__.py 类似,import 一个包会自动查找内部的 __init__.py,而 cimport 一个包会自动查找内部的 __init__.pxd。


小结




C、C++ 头文件通过 #include 命令进行访问,该命令会对相应的头文件进行包含。而 Cython 的 cimport 更智能,也更不容易出错,我们可以把它看做是一个使用命名空间的编译时导入语句。

而早期的 Cython 没有 cimport 语句,只有一个在源码级对文件进行包含的 include 语句,但是有了 cimport 之后就更加智能了

E N D


相关文章
|
7月前
|
编译器
预定义宏
预定义宏。
40 11
|
自然语言处理 编译器 程序员
C基础语法(编译与预定义)
C基础语法(编译与预定义)
88 0
|
7月前
|
监控
第六十章 使用 ^PERFSAMPLE 监控进程 - 预定义分析示例
第六十章 使用 ^PERFSAMPLE 监控进程 - 预定义分析示例
38 0
|
7月前
|
Shell Linux
Linux系统中预定义文件的执行顺序和依赖关系
Linux系统中预定义文件的执行顺序和依赖关系
42 1
|
7月前
预定义字符集
预定义字符集
75 1
|
Shell
makefile 自定义,预定义函数
makefile 自定义,预定义函数
111 0
|
并行计算 PHP
PHP 预定义常量
PHP 预定义常量
58 0
|
前端开发
5月18日,预定你了!
5月18日,预定你了!
|
Go Windows
Go-保留关键字和预定义标识符
Go-保留关键字和预定义标识符
129 0
Go-保留关键字和预定义标识符
|
Shell
C编译中如何向代码中传递一个预定义字串
C编译中如何向代码中传递一个预定义字串
80 0