古明地觉的编程教室

如何优雅地将 C 指针返回给 Python

哈哈,想不到吧,Cython 系列居然诈尸了。关于 Cython 的语法我们已经介绍完了,接下来会介绍一些使用 Cython 时的小技巧以及一些遗漏的细节。

而本次介绍的是如何将一个 C 指针优雅地返回给 Python。

假设有一个 C 头文件 rect.h 和一个 C 源文件 rect.c,其中 rect.h 的内容如下:

typedef struct {
    int x;
    int y;
} 
Rectangle;Rectangle* create_rectangle(int, int);

rect.c 的内容如下:

#include "rect.h"
#include <stdlib.h>
Rectangle* create_rectangle(int x, int y) {
    
Rectangle* r = (Rectangle*)malloc(sizeof(Rectangle));
   
 r -> x = x;
r -> y = y;
return r;

}

然后我们要基于 create_rectangle 这个 C 函数构建相应的结构体实例,拿到它的指针并返回给 Python,那么该怎么做呢?首先 Python 在语言层面没有指针的概念,所以如果想返回指针的话,那么只能将指针包上一层。

编写 Cython 源文件:

from libc.stdlib cimport free

cdef

extern from "./rect.h":
    # 我们暴露给 Python 的类也叫 Rectangle
    # 因此为了避免冲突,这里起个别名
    ctypedef 
struct_Rectangle "Rectangle":
        
int x
        
int y_Rectangle* create_rectangle (int x, int y);

cdef class Rectangle:
    """
    定义一个扩展类,用于包装结构体指针
    """

    cdef

_Rectangle *rect

    def __init__(self, 

int x, int y):
        # 创建结构体实例,拿到它的指针保存起来
        # 显然这个 rect 属性不能暴露给外界
        self.rect = create_rectangle(x, y)

    # 提供专门的接口用于操作属性

property x:
        def __get__(self):
            return self.rect.x

        def __set__(self, 

int x):
            self.rect.x = x
property y:
        def __get__(self):
            return self.rect.y

        def __set__(self, 

int y):
            self.rect.y = y

    def get_area(self):
        """返回矩形的面积"""
        return self.x * self.y

    def __dealloc__(self):
        # 当实例被销毁时
        # 释放堆区的 C 结构体内存
        if self.rect != NULL:
            free(<

void *> self.rect)
            print("堆内存被释放")

文件名叫 cython_test.pyx,我们编译一下,然后导入测试:

import cython_test

rect = cython_test.Rectangle(10, 20)
print(rect.x, rect.y)  # 10 20
print(rect.get_area())  # 200

rect.x = 100
rect.y = 200
print(rect.x, rect.y)  # 100 200
print(rect.get_area())  # 20000

del rect
print("--------------")
"""
堆内存被释放
--------------
"""

从目前来看效果还是不错的,因为 Python 无法操作指针,因此直接返回指针是会报错的,只能在 Cython 内部操作。但可以自定义一个类,将指针隐藏在里面,然后再专门提供一些接口,供外界使用。外界调用相应的接口,然后再由 Cython 在内部操作,非常完美。