如何获取指向通用PyObject*内部数据的指针?

4

我试图获取在 PyObject* 中保存的数据的内存地址(特定于 Python.h 3.8.2),以便我可以将其复制到缓冲区中。我只能找到如何从对象中复制数据,但没有关于如何获取指针的信息。假设我有这个对象 data ...

PyObject* data = PyLong_FromLong(100L);

目前,似乎我唯一的选择是将这些数据复制出来,然后使用临时变量的地址进行 memcpy,以使数据进入缓冲区。

long temp = PyLong_AsLong(data);
memcpy(buffer, &temp, 8);

由于这样的操作会被执行成千上万次,所以如果我能够获得数据的内存地址并直接将其复制到我的缓冲区中,那么我认为速度会更快。

memcpy(buffer, data->address_to_data(), 8)

不是使用额外的临时变量,而是直接操作。

有人知道如何从 PyObject* 包装器中获取 long 值的内存地址吗?

感谢帮助!


你认为使用 PyLong_AsVoidPtr 怎么样? - JTejedor
值得指出的几件事情:1)PyLong可以存储任意大的数字(即比C语言的“long”要大得多),因此实际上没有内部的“long”可供访问。2)“通用Python对象”可以包含指向其他Python对象的指针,在复制时需要小心处理。3)如果您正在寻找快速访问数值的方法,那么可能应该使用类似于array.array的东西,并使用缓冲区协议。 - DavidW
@DavidW 感谢您的详细评论。您能再详细介绍一下“使用缓冲区协议的array.array”吗?我正在寻找将值放入缓冲区的最快方法。 - Tyler Weiss
3个回答

3

这似乎是一个X-Y问题(即你认为你需要在C级别从一堆Python对象中提取数据,但实际上你会受益于拥有一个公开所有数据的单个Python对象)。

Python int可以存储(几乎)任意大的数字:

>>> 1000**1000  # creates a very big int

即它并未以 C long 的形式在内部存储。内部将其存储为整数的数组(ob_digits),该数组的大小为 ob_size,其格式略微奇特且对您没有太大用处。但是,如果您真的想要复制它,您应该将对象指针强制转换为 PyLongObject*,然后执行 memcpy(&dest, my_int->ob_digit, sizeof(digit)*abs(my_int->ob_size));。我建议您不要这样做,因为您很难使用这些数据。
显然,这仅适用于您知道自己有一个 Python int 的情况。对于“通用 PyObject*”,这种方法行不通,因为通用 PyObject* 可包含几乎任何数据。这包括需要所有权和/或引用计数的指针(尤其适用于包含其他 PyObject 的任何 PyObject)。
我认为您实际上想要的是将数据存储在一个大型的 C 整数数组中。可以使用 array.arraynumpy.array 或其他各种类实现此目的。
在 C 级别上,这些对象支持缓冲区协议,它们将内部数组公开给 C,允许从 C 访问、复制、操作等每个值。
下面是一些快速的未经测试的示例代码:
Py_Buffer view;
view.format = "l"; // request an array of longs
if (PyObject_GetBuffer(obj, &view, PyBUF_CONTIG | PyBUF_FORMAT | PyBUF_WRITABLE ) == -1) {
   // failed
   return NULL;
}

// you want to check that view.ndim == 1 (for a simple 1D array)
long* data = (long*)view.buf;
// At this point you can access data as a C array of length view.len

// When you've finished;
PyBuffer_Release(view);

0

这似乎是与数据结构抽象相关的设计问题。通常,希望为用户提供不透明的数据结构或指针。访问内部元素需要调用方法(或函数)。

来自https://docs.python.org/3/c-api/long.html

PyObject* PyLong_FromLong(long v)

Return value: New reference.
Return a new PyLongObject object from v, or NULL on failure.

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.

该调用可能会进行内部转换为PyLongObject,它可以是其内部对象的链接。如果您传递-5到256之间的值,则会将您的整数替换为其内部对象。对于其他值,将创建一个新对象。即使您找到了内部内存位置,也不能保证行为始终保持一致。

PyObject似乎被设计为不透明的。请将其视为这样处理。


谢谢您的详细回复,将PyObject*放入缓冲区作为long类型的最佳方法是什么?我在原帖中遵循的步骤是最好的吗? - Tyler Weiss
你使用的方法有什么问题吗?= 目前似乎我唯一的选择是将数据复制出来,然后使用临时变量的地址进行memcpy long temp = PyLong_AsLong(data); memcpy(buffer, &temp, 8); - moi
如果我没有从长值PyLong_AsLong构造到我的临时变量中进行额外复制,那么速度会更快。理想情况下,应该有一些Py函数可以直接构造长值到缓冲区中。我知道很可能没有这个选项。我只是想确保我在原始帖子中的选择是我性能最佳的选择。 - Tyler Weiss
这不是最佳选择 - 但不透明对象的本质要求缺乏直接内存访问。这与对象的工作方式有关。可悲的是,您必须接受这种性能损失。 - moi

0

有一个内部的CPython函数可以做类似于您想要的事情,叫做_PyLong_AsByteArray

它似乎从一个名为ob_digit的字段中读取所需的字节,但我并不完全理解整个函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接