INCREF требуется при возврате аргумента из функции расширения Python C?

Этот вопрос довольно прост, но поможет укрепить мое понимание. Я знаю, что аргументы функций расширения C гарантированно будут живыми ссылками на протяжении всего кода C (если только вручную не DECREF). Однако, если у меня есть код расширения C, который возвращает PyObject*, присутствующий в списке его аргументов, нужно ли мне добавлять аргумент INCREF перед его возвратом? То есть, какой из следующих двух правилен:

static PyObject return_item(PyObject *self, PyObject *item)
{
    // manipulate item
    return item;
}

or

static PyObject return_item(PyObject *self, PyObject *item)
{
    // manipulate item
    Py_INCREF(item);
    return item;
}

На основе https://docs.python.org/3/extending/extending.html#ownership-rules, в котором говорится

Ссылка на объект, возвращаемая из функции C, которая вызывается из Python, должна быть ссылкой в ​​собственности — право собственности передается от функции к вызывающей стороне.

и Возврат объектов в Python из C Я предполагаю, что это последнее (INCREFing - это да ладно) но я хочу быть уверен.


person Michael Carilli    schedule 26.08.2019    source источник


Ответы (2)


Если кто-то вызывает функцию return_item из Python, он может сделать это:

something = Something()
something_else = return_item(something)
del something

Если бы return_item вернул не переданный аргумент, а что-то другое, можно было бы ожидать, что в этот момент переданный something должен быть освобожден из памяти, потому что его счетчик ссылок падает до нуля.

Если вы не сделаете Py_INCREF и вернете тот же объект, это все равно произойдет - счетчик ссылок на объект упадет до 0, и у вас будет недопустимый объект в something_else.

TL;DR: Да, вы должны Py_INCREF, потому что вы создали еще одну ссылку на этот объект, вернув его из функции.

person zvone    schedule 26.08.2019

Вы не хотите увеличивать счетчик ссылок на объект перед его возвратом. Это создаст утечку памяти, которая предотвратит сборку мусора для объекта.

Думайте об увеличении счетчика ссылок как о заявлении: «Я использую эту память. Пожалуйста, не освобождайте ее». Когда вы вводите код C, вы «заимствуете» ссылку из Python, но когда вы выходите из кода C, вы заканчиваете работу с объектом и вам больше не нужна ссылка.

Переменные и базовая память в Python разделены, что позволяет более эффективно использовать память (подробнее). В другом ответе упускается тот факт, что присваивание something_else увеличивает счетчик ссылок для базовой памяти. Вы можете убедиться в этом сами с помощью sys.getrefcount.

import sys
something = "hello"
print(sys.getrefcount(something))       # 2 (getrefcount uses a reference)

something_else = something
print(sys.getrefcount(something_else))  # 3 (same memory as something)

del something
print(sys.getrefcount(something_else))  # 2
print(something_else)                   # "hello"

И something, и something_else относятся к одной и той же памяти (строка, содержащая текст «привет»). Удаление одного не влияет на другое. Несмотря на то, что в вашем коде используется функция C, основной принцип тот же. Попробуйте распечатать количество ссылок с обеими версиями вашей функции C, и это будет более понятно.

Чего вам нельзя делать, так это вызывать Py_DECREF перед возвратом объекта. В этом случае счетчик ссылок может упасть до нуля, и возвращаемая вещь будет полностью недействительной.

person Robin Betz    schedule 26.09.2019