像 C, Go, Rust 这样的静态语言都有指针的概念,通过对指针解引用即可拿到指针指向的内存。而指针在 Cython 里面也是支持的,但前提指针指向的必须是 C 类型的变量。
cdef int n = 123 # 拿到 n 的地址 cdef int *p = &n # Cython 里面不能使用 *p 来解引用 # 因为 * 有着其它的含义 # 我们需要使用 p[0] 来解引用 p[0] += 1 # 打印 n, 会发现被修改了 # 因为 p 指向的内存中保存的就是 n print(n) # 124
当对象是 C 类型的变量时,那么可以获取指针,因为对于 C 而言,变量就是内存的别名。但是 Python 不行,因为 Python 的变量存储就是对象的地址,我们可以通过 id 函数查看。
cdef list names = ["satori", "koishi", "marisa"] # 我们不能使用 &names 获取列表对象的指针 # 因为 names 保存的本身就是列表对象的地址 print(id(names)) # 1918122496768 # 只不过 Python 在语言层面上摒弃了指针 # 所以我们叫它地址,但它不具备指针的含义 # 虽然以前一直说 Python 的变量本质上是 PyObject * # 但那是站在 C 的角度上来说的,而 Python 没有指针 # 因此 id(names) 返回的就是一串数字 # 不过这里是 Cython,Cython 同时理解 C 和 Python # 所以我们可以把 Python 的变量当成指针来用 print( <Py_ssize_t><void *>names ) # 1918122496768 # 打印的结果仍然是一串数字
那么问题来了,我们能不能根据这一串数字,再反推出 Python 对象呢?
比如外界的 Python 代码将对象的地址传过来,我们根据地址来反推出这个对象是什么,你觉得可以办到吗?如果是纯 Python 的话,应该是办不到的,但有了 Cython 一切都有可能。
# 一个纯 Python 类型的变量,指向一个列表 names = ["satori", "koishi", "marisa"] # 拿到它的地址 # 此时的 address 就是一串数字 address = id(names) # 下面我们要进行反推了 print( <object><void *><Py_ssize_t>address ) # ['satori', 'koishi', 'marisa']
怎么样,是不是很神奇呢?首先 void * 可以转成整数,那么整数也可以变成 void *,只不过这个整数需要是 C 的整数。转成 void * 之后,再转成 object 类型即可。
为了更直观地看到现象,我们封装一个函数:
def infer_object(Py_ssize_t address): return <object> <void *> address
文件名为 cython_test.pyx,我们测试一下:
import pyximport pyximport.install(language_level=3) from cython_test import infer_object number = 666 print( infer_object(id(number)) ) # 666 name = "古明地觉" print( infer_object(id(name)) ) # 古明地觉 class Girl: name = "古明地恋" age = 15 print( infer_object(id(Girl)).name, infer_object(id(Girl)).age ) # 古明地恋 15
我们后续会介绍如何在 Cython 中引入 C,即便是 C 函数返回一个地址,我们也是可以拿到相应的值的。
E N D