编写 Cython 扩展:如何从 Python 访问 C 结构内部数据?
Writing Cython extension: how to access a C struct internal data from Python?
免责声明: 我从 Python Cookbook (O'Reilly) 中获取了以下示例。
假设我有以下简单的 struct
:
typedef struct {
double x,y;
} Point;
具有计算两个 Point
之间的欧氏距离的函数:
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
和函数,我可以轻松地使用 ctypes
或 cffi
但我想学习如何编写 Cython 扩展。这里不需要setup.py
所以不需要post它。
现在你可以看到上面的构造函数允许我们做
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 class 中,成员 (self.x
、self.y
) 也可以在没有任何 getter 和 setter 的情况下访问。
我可以编写充当中间步骤的函数:
double x(Point* p);
double y(Point* p);
但是我不确定如何包装这些以及如何在 table 方法中描述它们的调用。
我该怎么做?我想要一个简单的 p1.x
来获取 Python.
中我的 Point
结构的 x
我最初对这个问题有点困惑,因为它似乎没有 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 - 这不是一个明智的解决方案,我不知道他们为什么推荐它。
免责声明: 我从 Python Cookbook (O'Reilly) 中获取了以下示例。
假设我有以下简单的 struct
:
typedef struct {
double x,y;
} Point;
具有计算两个 Point
之间的欧氏距离的函数:
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
和函数,我可以轻松地使用 ctypes
或 cffi
但我想学习如何编写 Cython 扩展。这里不需要setup.py
所以不需要post它。
现在你可以看到上面的构造函数允许我们做
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 class 中,成员 (self.x
、self.y
) 也可以在没有任何 getter 和 setter 的情况下访问。
我可以编写充当中间步骤的函数:
double x(Point* p);
double y(Point* p);
但是我不确定如何包装这些以及如何在 table 方法中描述它们的调用。
我该怎么做?我想要一个简单的 p1.x
来获取 Python.
Point
结构的 x
我最初对这个问题有点困惑,因为它似乎没有 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 - 这不是一个明智的解决方案,我不知道他们为什么推荐它。