Cyclic GC rules for subtyped objects with tp_dictoffset

Hrvoje Niksic hniksic at
Tue Mar 31 09:27:45 CEST 2009

[ Questions such as this might be better suited for the capi-sig list, ]

BChess <bchess at> writes:

> I'm writing a new PyTypeObject that is base type, supports cyclic
> GC, and has a tp_dictoffset.  If my type is sub-typed by a python
> class, what exactly are the rules for how I'm supposed to treat my
> PyDict object with regards to cyclic GC?  Do I still visit it in my
> traverse () function if I'm subtyped?  Do I decrement the refcount
> upon dealloc?  By the documentation, I'm assuming I should always be
> using _PyObject_GetDictPtr() to be accessing the dictionary, which I
> do.  But visiting the dictionary in traverse() in the case it's
> subtyped results in a crash in weakrefobject.c.  I'm using Python
> 2.5.

First off, if your class is intended only as a base class, are you
aware that simply inheriting from a dictless class adds a dict
automatically?  For example, the base "object" type has no dict, but
inheriting from it automatically adds one (unless you override that
using __slots__).  Having said that, I'll assume that the base class
is usable on its own and its direct instances need to have a dict as

I'm not sure if this kind of detail is explicitly documented, but as
far as the implementation goes, the answer to your question is in
Objects/typeobject.c:subtype_traverse.  That function gets called to
traverse instances of heap types (python subclasses of built-in
classes such as yours).  It contains code like this:

     if (type->tp_dictoffset != base->tp_dictoffset) {
         PyObject **dictptr = _PyObject_GetDictPtr(self);
             if (dictptr && *dictptr)

According to this, the base class is responsible for visiting its dict
in its tp_traverse, and the subtype only visits the dict it added
(which is why its location differs).  Note that visiting an object
twice still shouldn't cause a crash; objects may be and are visited an
arbitrary number of times, and it's up to the GC to ignore those it
has already seen.  So it's possible that you have a bug elsewhere in
the code.

As far as the decrementing goes, the rule of thumb is: if you created
it, you get to decref it.  subtype_dealloc contains very similar

	/* If we added a dict, DECREF it */
	if (type->tp_dictoffset && !base->tp_dictoffset) {
		PyObject **dictptr = _PyObject_GetDictPtr(self);
		if (dictptr != NULL) {
			PyObject *dict = *dictptr;
			if (dict != NULL) {
				*dictptr = NULL;

So, if the subtype added a dict, it was responsible for creating it
and it will decref it.  If the dict was created by you, it's up to you
to dispose of it.

More information about the Python-list mailing list