Правильная циклическая сборка мусора в модулях расширения

В двух разделах документа Python 2.7 упоминается добавление поддержки циклической сборки мусора (CGC) для объектов контейнера, определенных в модулях расширения.

Справочное руководство по API Python / C дает два правила:

  1. Память для объекта должна быть выделена с помощью PyObject_GC_New() или PyObject_GC_NewVar() .
  2. После того, как все поля, которые могут содержать ссылки на другие контейнеры, будут инициализированы, он должен вызвать PyObject_GC_Track() .

В то время как в расширении и встраивании интерпретатора Python для примера Noddy кажется, что добавление флага Py_TPFLAGS_HAVE_GC и заполнение tp_traverse и tp_clear слотов будет достаточным для поддержки CGC. И два вышеприведенных правила НЕ применяются практически.

Когда я модифицировал пример Noddy чтобы фактически следовать правилам PyObject_GC_New() / PyObject_GC_Del() и PyObject_Track() / PyObject_GC_UnTrack() , он неожиданно поднял ошибку утверждения, говоря:

Модули / gcmodule.c: 348: visit_decref: утверждение «gc-> gc.gc_refs! = 0» не выполнено. пересчет был слишком мал

Это приводит к моей путанице в правильном / безопасном способе реализации CGC. Может ли кто-нибудь давать советы или, предпочтительно, опрятный пример контейнерного объекта с поддержкой CGC?

    В большинстве нормальных случаев вам не нужно делать слежение / выслеживание самостоятельно. Это описано в документации, однако это не делается четко. В случае примера Noddy вы определенно этого не делаете.

    Короткий вариант заключается в том, что TypeObject содержит два указателя на функции: tp_alloc и tp_free . По умолчанию tp_alloc вызывает все правильные функции при создании класса (если установлен Py_TPFLAGS_HAVE_GC ), а tp_free освобождает класс от уничтожения.

    В документации Noddy говорится (в конце раздела):

    Это в значительной степени. Если бы мы написали пользовательские tp_alloc или tp_free , нам нужно было бы их модифицировать для сбора циклических мусора. Большинство расширений будут использовать предоставленные версии автоматически.

    К сожалению, одно место, которое не дает понять, что вам не нужно это делать самостоятельно, – это документация Supporting Cyclic Garbage Collection .


    Деталь:

    Noddy выделяются с помощью функции Noddy_new помещенной в слоты tp_new объекта TypeObject . Согласно документации , главное, что должна выполнить «новая» функция, – это tp_alloc слот tp_alloc . Обычно вы не пишете tp_alloc , а по умолчанию – PyType_GenericAlloc() .

    Глядя на PyType_GenericAlloc() в источнике Python, отображается ряд разделов, где он изменяется на основе PyType_IS_GC(type) . Сначала он вызывает _PyObject_GC_Malloc вместо PyObject_Malloc , а второй вызывает _PyObject_GC_TRACK(obj) . [Обратите внимание, что все, что PyObject_New действительно делает, это вызов PyObject_Malloc а затем tp_init .]

    Аналогично, при освобождении вы вызываете слот tp_free , который автоматически устанавливается в PyObject_GC_Del для классов с Py_TPFLAGS_HAVE_GC . PyObject_GC_Del включает в себя код, который выполняет то же самое, что и PyObject_GC_UnTrack поэтому вызов для проверки не нужен.

    Я сам не очень опытен в C API, чтобы дать вам какие-либо советы, но примеров реализации самих контейнеров Python достаточно.

    Лично я начинаю с реализации кортежа во-первых, поскольку он неизменен: Objects / tupleobject.c . Затем перейдите к list dict , list и set реализации для дальнейших заметок в изменяемых контейнерах:

    • Объекты / dictobject.c
    • Объекты / listobject.c
    • Объекты / setobject.c

    Я не могу не заметить, что есть вызовы PyObject_GC_New() , PyObject_GC_NewVar() и PyObject_GC_Track() всем протяжении, а также набор Py_TPFLAGS_HAVE_GC .