CPython:有没有什么方法可以将 PyObject 作为序列遍历,而不必随后创建和销毁对象?

CPython: Is there any way to traverse a PyObject as a sequence without having to create and destroy objects subsequently?

为了制作一个充当序列的 PyObject,我刚刚向 PyTypeObject.

的变量 tp_as_sequence 的插槽 sq_item 添加了一个函数

这是我的 sq_item 函数:

static PyObject *py_myseq__sq_item(PyMySeq *self, unsigned int keynum)
{
    if (keynum < 0) keynum += self->len; /* ex.:>>> my_seq[-1] */
    if (keynum >= 0 && keynum < self->len) {
        MyItem *item = &self->items[keynum];

        return PyMyItem_New(item);
    }

    PyErr_Format(PyExc_IndexError,
                 "PyMySeq[index]: index %d out of range", keynum);
    return NULL;
}

每次我想获取序列中的一项时,都会调用PyMyItem_New函数。这在大多数情况下都很好。但是对于像使用 for 循环这样的情况,这是非常低效的:

for i in myPyObjSeq:
    print(i)

如果我的序列有一百万个项目。项目将在这个循环中创建和销毁 100 万次!!!

问题是:有什么办法可以避免吗???

您有几个选择:

  • 首先,在CPython API中,分配和释放许多小对象是很常见的。 CPython 有一个为此优化的分配器,因此通常不能避免。
    See this question for details.
  • 如果您需要遍历数百万个项目,您可能需要考虑实现一个迭代器,这样您就可以遍历项目但永远不会一次分配所有项目。
  • 另一种选择,(并非如此 Pythonic) - 是在您的序列上有一个方法,该方法接受一个可调用对象。与将回调传递给 list.sort(key=function).
    的方式类似,在这种情况下,您可以将相同的对象传递给每个函数,并修改 index.
    但是不要被愚弄了,调用函数也创建了 PyObject 的!
  • 如果数据是原始 C 结构,您可以使用缓冲区接口公开,也请参见内存视图。
  • 您总是可以拥有一个就地修改的 Python 对象,但这会导致您的 API 用户有些困惑,因为他们可能会访问索引而没有意识到进一步的访问会更改另一个变量 (不好的做法,不要这样做`)