【问题标题】:Writing Cython extension: how to access a C struct internal data from Python?编写 Cython 扩展:如何从 Python 访问 C 结构内部数据?
【发布时间】:2017-05-07 20:31:28
【问题描述】:

免责声明:我从 Python Cookbook (O'Reilly) 中获取了以下示例。

假设我有以下简单的struct

typedef struct {
  double x,y;
} Point;

使用计算两个Points 之间的欧几里得距离的函数:

extern double distance(Point* p1, Point* p2);

所有这些都是名为 points 的共享库的一部分:

  • points.h - 头文件
  • points.c - 源文件
  • libpoints.so - 库文件(Cython 扩展链接到它)

我已经创建了我的包装 Python 脚本(称为 pypoints.py):

#include "Python.h"
#include "points.h"


// Destructor for a Point instance
static void del_Point(PyObject* obj) {
  // ...
}

// Constructor for a Point instance
static void py_Point(PyObject* obj) {
  // ...
}

// Wrapper for the distance function
static PyObject* py_distance(PyObject* self, PyObject* arg) {
  // ...
}

// Method table
static PyMethodDef PointsMethods[] = {
  {"Point", py_Point, METH_VARARGS, "Constructor for a Point"},
  {"distance", py_distance, METH_VARARGS, "Calculate Euclidean distance between two Points"}
}

// Module description
static struct PyModuleDef pointsmodule = {
  PyModuleDef_HEAD_INIT,
  "points", // Name of the module; use "import points" to use
  "A module for working with points", // Doc string for the module
  -1,
  PointsMethods // Methods provided by the module
}

请注意,这只是一个示例。对于struct 和上面的函数,我可以轻松使用ctypescffi,但我想学习如何编写 Cython 扩展。 setup.py 此处不需要,因此无需发布。

现在你可以看到上面的构造函数允许我们这样做

import points

p1 = points.Point(1, 2)   # Calls py_Point(...)
p2 = points.Point(-3, 7)  # Calls py_Point(...)

dist = points.distance(p1, p2)

效果很好。但是,如果我想实际访问Point 结构的内部结构怎么办?例如我会怎么做

print("p1(x: " + str(p1.x) + ", y: " + str(p1.y))

如您所知,可以直接访问 struct 内部(如果我们使用 C++ 术语,我们可以说所有 struct 成员都是 public)所以在 C 代码中我们可以轻松地做到这一点

Point p1 = {.x = 1., .y = 2.};
printf("p1(x: %f, y: %f)", p1.x, p1.y)

在 Python 中,类成员(self.xself.y)也可以在没有任何 getter 和 setter 的情况下访问。

我可以编写充当中间步骤的函数:

double x(Point* p);
double y(Point* p);

但是我不确定如何包装这些以及如何在方法表中描述它们的调用。

我该怎么做?我想要一个简单的p1.x 来在Python 中获取我的Point 结构的x

【问题讨论】:

  • double x(Point* p); 是一个名为 x 的函数的函数声明,它接受一个指向 Point 的指针作为参数并返回一个 double — 所以你应该能够包装 (以及写)它。 y 函数也是如此。
  • 要使用p1.xp1.y 表示法Python,给定一个类似Point 的类,您可以为一个名为x 的属性创建属性(带有关联的getter 和可能的setter)和另一个一个为y。不确定是否可以为 Cython 实现属性。

标签: python c struct wrapper cython


【解决方案1】:

我最初对这个问题有点困惑,因为它似乎没有 Cython 内容(抱歉,由于这种混乱导致编辑混乱)。

我不建议关注Python cookbook uses Cython in a very odd way。出于某种原因,它想使用我以前从未在 Cython 中使用过的 PyCapsules。

# tell Cython about what's in "points.h"
# (this does match the cookbook version)

cdef extern from "points.h"
    ctypedef struct Point:
        double x
        double y

    double distance(Point *, Point *)

# Then we describe a class that has a Point member "pt"
cdef class Py_Point:
    cdef Point pt

    def __init__(self,x,y):
        self.pt.x = x
        self.pt.y = y

    # define properties in the normal Python way
    @property
    def x(self):
        return self.pt.x

    @x.setter
    def x(self,val):
        self.pt.x = val

    @property
    def y(self):
        return self.pt.y

    @y.setter
    def y(self,val):
        self.pt.y = val

def py_distance(Py_Point a, Py_Point b):
    return distance(&a.pt,&b.pt) # get addresses of the Point members

然后你可以编译它并在 Python 中使用它

from whatever_your_module_is_called import *

# create a couple of points
pt1 = Py_Point(1.3,4.5)
pt2 = Py_Point(1.5,0)

print(pt1.x, pt1.y) # access the members

print(py_distance(pt1,pt2)) # distance between the two

为了公平起见,Python Cookbook 然后给出了第二个示例,该示例与我所做的非常相似(但使用了从 Cython 不支持类似 Python 的方法开始的稍旧的属性语法)。因此,如果您进一步阅读,您将不需要这个问题。但是避免混合 Cython 和 pycapsules - 这不是一个明智的解决方案,我不知道他们为什么推荐它。

【讨论】:

  • 我不明白答案。这是否意味着您必须使用 cython 并且不推荐使用 c API?
  • 否 - C API 未被弃用。但是,如果您已经决定使用 Cython,那么您最好正确使用它,而不是半心半意地混合 Cython 和 PyCapsules。
猜你喜欢
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
  • 2012-10-17
  • 1970-01-01
  • 1970-01-01
  • 2016-10-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多