将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()

变化是,

  1. Py_TPFLAGS_BASETYPE添加到tp_flags成员。这允许类型是可子类化的,但是后来不再使用tp_init函数,而初始化需要在tp_new中执行 - 我不知道它为什么会这样,恕我直言 - 这是愚蠢的 - 。
  2. 删除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.bufState的演员阵容很好,因为view.buf*void类型。然而,从州到焦的演员不是。理想情况下,这将是一个无效指针。

References: