Почему мы почти всегда должны переназначать элементы объекта, прежде чем уменьшать их количество ссылок?

#python #python-c-api #python-extensions

Вопрос:

Я читал этот учебник, чтобы узнать, как написать модуль расширения C для определения новых типов. В какой-то момент tp_init была дана следующая реализация:

 static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     amp;first, amp;last,
                                     amp;self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}
 

Проблема, с которой я сталкиваюсь, заключается в том, что я не вижу никакого вреда в том, чтобы назначить первого участника таким образом:

 if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}
 

С другой стороны, упомянутый учебник предостерегает нас от его использования, потому что:

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

Далее в нем также говорится::

Чтобы быть параноиком и защититься от такой возможности, мы почти всегда переназначаем участников, прежде чем уменьшать их количество ссылок

Принимая во внимание эти предупреждения, я все еще не вижу никаких практических различий между этими двумя способами. Итак, почему я должен использовать первый, который переназначает участника first новому PyObject, прежде чем уменьшать количество ссылок на старый PyObject?

Комментарии:

1. Я попытался ответить на этот вопрос, но в итоге удалил его, потому что я просто повторял документацию. Возможно, вы упускаете деталь, которая Py_XDECREF может привести к выполнению произвольного кода Python, и функция C не будет выполняться до тех пор, пока этот код Python не завершит выполнение?

2. Я полагаю, что произвольный код Python, на который вы ссылаетесь, не будет выполняться до тех пор, пока не включится сборщик мусора и количество ссылок PyObject не достигнет нуля. Чего я не могу понять, так это того, как рекомендуемый способ позволяет нам избежать этой проблемы по сравнению с не рекомендуемым способом?

3. Количество ссылок на объекты может достигать 0 при любом уменьшении. «Сборщик мусора» — это отдельная функция, которая иногда запускается для поиска циклических ссылок.