将python字节转换为c结构
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将python字节转换为c结构相关的知识,希望对你有一定的参考价值。
所以我正在优化玩游戏机器人的游戏,并且在纯python中已经没有优化了。目前,大部分时间用于将一个游戏状态转换为用于alpha-beta搜索的下一个游戏状态。目前的想法是我可以通过在C中编写状态转换代码来加快速度。我的问题来自于尝试将python状态转换为C可以再次运行的结构。
目前,状态由字节字符串唯一表示:
import itertools
import struct
BINCODE = struct.Struct('BBBBBBBBBBBBBBb')
class State:
__slots__ = '_bstring'
TOP = 1
BOTTOM = 0
def __init__(self, seeds=4, *, top=None, bottom=None, turn=0):
top = top if top else [seeds] * 6 + [0]
bottom = bottom if bottom else [seeds] * 6 + [0]
self._bstring = BINCODE.pack(*itertools.chain(bottom, top), turn)
@property
def top():
...
这个想法是,state._bstring,它已经被方便地打包为二进制数据,可以很好地转换成类似于这样的c结构:
struct State
{
unsigned int bottom[7];
unsigned int top[7];
int turn;
}
我的C代码可以操作,生成结果C状态作为新的二进制数据,并直接插入新的python State
对象。
但是,我似乎无法找到有关如何解决这个问题的任何信息。我能找到的几乎所有信息都是关于从文件中打包和解包C数据。我曾考虑在bytes对象上使用PyObject_GetBuffer
,但游戏逻辑非常复杂,我更喜欢将数据作为结构而不是数组来处理。此外,我想将复制量减少到最低限度。
我调查的另一个选项是使用在C中定义的PyCapsule
对象作为python的新状态,但是我将失去所有State类的python特定功能。我真的宁愿将python代码的更改保持在绝对最小值,因为许多以前的python优化都依赖于数据格式。
Cython似乎没有办法将二进制数据强制转换为C结构指针。重写State
与numba兼容会丢失关键功能,如独特的哈希等。
似乎应该有一个相当直接的方式来做到这一点,但我似乎无法找到它。任何和所有的帮助将不胜感激。
你最好写一个简单的python模块,如下所示,
#include <Python.h>
struct State {
unsigned int bottom[7];
unsigned int top[7];
int turn;
};
struct PyState {
PyObject_HEAD
struct State *internal;
};
static void PyState_free(PyObject *self);
static PyMethodDef py_mygamestate_module_methods[] = {{NULL, NULL, 0, NULL}};
static struct PyModuleDef py_mygamestate_module = {
PyModuleDef_HEAD_INIT,
/* name of module */
"mygamestate",
/* module documentation, may be NULL */
NULL,
/* size of per-interpreter state of the module, or -1
* if the module keeps state in global variables.
*/
-1,
py_mygamestate_module_methods,
NULL,
NULL,
NULL,
NULL
};
static PyObject *py_state_show(PyObject *self, PyObject *args);
static PyMethodDef py_state_methods[] = {
{"show", py_state_show, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
static PyObject *py_state_new(PyTypeObject *type, PyObject *parent, PyObject *args);
#define Py_BASE_TYPE_FLAGS (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE)
static PyTypeObject py_state_type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"State", /* tp_name */
sizeof(struct PyState), /* tp_basicsize */
0, /* tp_itemsize */
PyState_free, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_BASE_TYPE_FLAGS, /* tp_flags */
"Docstring", /* tp_doc */
0, /* tp_travers */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_next */
py_state_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&PyBaseObject_Type, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
py_state_new, /* tp_new */
0, /* tp_free */
0, /* tp_is_gc */
0, /* tp_bases */
0, /* tp_mro */
0, /* tp_cache */
0, /* tp_subclasses */
0, /* tp_weaklist */
0, /* tp_del */
0, /* tp_version_tag */
0 /* tp_finalize */
};
static void
PyState_free(PyObject *self)
{
free(((struct PyState *) self)->internal);
}
static PyObject *
py_state_new(PyTypeObject *type, PyObject *parent, PyObject *args)
{
struct PyState *state;
PyObject *self;
self = PyType_GenericNew(type, parent, args);
if (self == NULL)
return NULL;
// Cast the object to the appropriate type
state = (struct PyState *) self;
// Initialize your internal structure
state->internal = malloc(sizeof(*state->internal));
if (state->internal == NULL)
return NULL;
memset(state->internal, 0, sizeof(*state->internal));
// This means no error occurred
return self;
}
static PyObject *
py_state_show(PyObject *self, PyObject *args)
{
struct State *state;
// Cast the object to the appropriate type
state = ((struct PyState *) self)->internal;
if (state == NULL)
return NULL;
fprintf(stdout, "bottom: ");
for (size_t i = 0; i < 7; ++i)
fprintf(stdout, "%d, ", state->bottom[i]);
fprintf(stdout, "top: ");
for (size_t i = 0; i < 7; ++i)
fprintf(stdout, "%d, ", state->bottom[i]);
fprintf(stdout, "turn: %d
", state->turn);
return self;
}
PyObject *
PyInit_mygamestate(void)
{
PyObject *module;
// Prepare the base classes to add them
if (PyType_Ready(&py_state_type) < 0)
return NULL;
// Create the apache module
module = PyModule_Create(&py_mygamestate_module);
if (module == NULL)
return NULL;
// Add the base classes
PyModule_AddObject(module, "State", (PyObject *) &py_state_type);
return module;
}
请注意,模块dll或so文件的名称应与PyInit_mygamestate
中下划线后面的部分匹配。
现在,如果您将so文件安装到site-packages目录,那么从python可以执行此操作
import mygamestate
state = mygamestate.State()
state.show()
这样你可以同时拥有任何类型,如python类型和c类型。
你当然可以将py_state_methods
数组增长到你想要的大小,并且有任何方法可以在python代码中使用。您还可以将参数传递给构造函数和每个方法,依此类推。
还有另一个数组,即py_mygamestate_module_methods
,它将是python代码中模块可直接访问的方法。
注意:原始代码已被修改,所以现在它允许继承,你可以做这样的事情
from mygamestate import State
class CustomState(State):
def __init__(self):
pass
# add all your custom methods here
def sample_method(self):
print('Cool, it works')
state = CustomState()
state.show()
state.sample_method()
变化是,
- 将
Py_TPFLAGS_BASETYPE
添加到tp_flags
成员。这允许类型是可子类化的,但是后来不再使用tp_init
函数,而初始化需要在tp_new
中执行 - 我不知道它为什么会这样,恕我直言 - 这是愚蠢的 - 。 - 删除
py_state_init
函数,而是创建一个py_state_new
函数,替换PyType_GenericNew
函数为我们自定义类型的tp_new
实例的PyTypeObject
函数。 我们调用原始的PyType_GenericNew()
来创建我们的类型对象,然后执行旧的py_state_init()
所做的事情来初始化我们的内部结构数据。
通过继承这个类,你可以拥有你想要的两件事。
好的,所以我已经找到了我的解决方案,而且相当直接。我似乎误解了Py_buffer
的工作方式。
python bytes
类型实现了缓冲协议,因此您可以使用PyObject_GetBuffer
函数来获取对底层数据的引用。 buffer.buf
指向数据作为void
指针,可以自由地投射到你想要的任何结构。
这是一些(简化的)代码:
#include <Python.h>
typedef struct State{
char board[14];
char turn;
} State;
static PyObject *py_after_move(PyObject *self, PyObject *args){
PyObject *buffobj;
Py_buffer view;
int move;
//get the passed PyObject
if (!PyArg_ParseTuple(args, "Oi", &buffobj, &move)) {
return NULL;
}
//get buffer info
if (PyObject_GetBuffer(buffobj, &view, PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT) == -1) {
return NULL;
}
//copy and cast as a state
State state;
memcpy(&state, view.buf, sizeof(state));
for (int i=0; i < 14; i++) {
state.board[i] += 1; /// example modifications
}
state.turn++;
//re-cast as characters and place in new bytes object
char *aschr = (char*) &state;
PyBuffer_Release(&view);
return Py_BuildValue("y#", aschr, sizeof(state));
}
/* python module boilerplate goes here */
在这里,我做了缓冲区的memcpy
,因为我不想改变输入。
Some Notes:
- 小心你的结构。上面的代码应该真正检查
view.len == 15
并提出错误,如果它没有。如果不这样做可能会导致seg-fault和安全问题 - 在C99中被认为是犹太人,不能指向任何非虚空的指针。所以在上面的代码中,从
view.buf
到State
的演员阵容很好,因为view.buf
是*void
类型。然而,从州到焦的演员不是。理想情况下,这将是一个无效指针。