Rewrote the docs for supporting cyclic garbage collection to reflect

the new way that once writes types.

Deleted the old section and sample code and added a new section
building on the Noddy example.
This commit is contained in:
Jim Fulton 2003-06-28 13:29:16 +00:00
parent 9c3e957251
commit 6c71091fbe
5 changed files with 394 additions and 103 deletions

View File

@ -1,79 +0,0 @@
#include "Python.h"
typedef struct {
PyObject_HEAD
PyObject *container;
} MyObject;
static int
my_traverse(MyObject *self, visitproc visit, void *arg)
{
if (self->container != NULL)
return visit(self->container, arg);
else
return 0;
}
static int
my_clear(MyObject *self)
{
Py_XDECREF(self->container);
self->container = NULL;
return 0;
}
static void
my_dealloc(MyObject *self)
{
PyObject_GC_UnTrack((PyObject *) self);
Py_XDECREF(self->container);
PyObject_GC_Del(self);
}
static PyTypeObject
MyObject_Type = {
PyObject_HEAD_INIT(NULL)
0,
"MyObject",
sizeof(MyObject),
0,
(destructor)my_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
0, /* tp_doc */
(traverseproc)my_traverse, /* tp_traverse */
(inquiry)my_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
};
/* This constructor should be made accessible from Python. */
static PyObject *
new_object(PyObject *unused, PyObject *args)
{
PyObject *container = NULL;
MyObject *result = NULL;
if (PyArg_ParseTuple(args, "|O:new_object", &container)) {
result = PyObject_GC_New(MyObject, &MyObject_Type);
if (result != NULL) {
result->container = container;
PyObject_GC_Track(result);
}
}
return (PyObject *) result;
}

View File

@ -670,6 +670,97 @@ We also rename the module initialization function and module name in
the initialization function, as we did before, and we add an extra
definition to the \file{setup.py} file.
\subsection{Supporting cyclic garbage collection}
Python has a cyclic-garbage collector that can identify unneeded
objects even when their reference counts are not zero. This can happen
when objects are involved in cycles. For example, consider:
\begin{verbatim}
>>> l = []
>>> l.append(l)
>>> del l
\end{verbatim}
In this example, we create a list that contains itself. When we delete
it, it still has a reference from itself. It's reference count doesn't
drop to zero. Fortunately, Python's cyclic-garbage collector will
eventually figure out that that the list is garbage and free it.
In the second version of the \class{Noddy} example, we allowed any
kind of object to be stored in the \member{first} or \member{last}
attributes. This means that \class{Noddy} objects can participate in
cycles:
\begin{verbatim}
>>> import noddy2
>>> n = noddy2.Noddy()
>>> l = [n]
>>> n.first = l
\end{verbatim}
This is pretty silly, but it gives us an excuse to add support for the
cyclic-garbage collector to the \class{Noddy} example. To support
cyclic garbage collection, types need to fill two slots and set a
class flag that enables these slots:
\verbatiminput{noddy4.c}
The traversal method provides access to subobjects that
could participate in cycles:
\begin{verbatim}
static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
if (self->first && visit(self->first, arg) < 0)
return -1;
if (self->last && visit(self->last, arg) < 0)
return -1;
return 0;
}
\end{verbatim}
For each subobject that can participate in cycles, we need to call the
\cfunction{visit} function passed to the traversal method passing the
subobject and the extra argument passed to the traversal method.
We also need to provide a method for clearing any subobjects that can
participate in cycles. We implement the method and reimplement the
deallocator to use it:
\begin{verbatim}
static int
Noddy_clear(Noddy *self)
{
Py_XDECREF(self->first);
self->first = NULL;
Py_XDECREF(self->last);
self->last = NULL;
return 0;
}
static void
Noddy_dealloc(Noddy* self)
{
Noddy_clear(self);
self->ob_type->tp_free((PyObject*)self);
}
\end{verbatim}
Finally, we add the \constant{Py_TPFLAGS_HAVE_GC} flag to the class flags:
\begin{verbatim}
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
\end{verbatim}
That's pretty much it. If we had written custom \member{tp_alloc} or
\member{tp_free} slots, we'd need to modify then for cyclic-garbage
collection. Most extensions will use the versions automatically
provided.
\section{Type Methods
\label{dnt-type-methods}}
@ -1304,30 +1395,6 @@ without setting an exception or it may set \exception{StopIteration};
avoiding the exception can yield slightly better performance. If an
actual error occurs, it should set an exception and return \NULL.
\subsection{Supporting the Cycle Collector
\label{example-cycle-support}}
This example shows only enough of the implementation of an extension
type to show how the garbage collector support needs to be added. It
shows the definition of the object structure, the
\member{tp_traverse}, \member{tp_clear} and \member{tp_dealloc}
implementations, the type structure, and a constructor --- the module
initialization needed to export the constructor to Python is not shown
as there are no special considerations there for the collector. To
make this interesting, assume that the module exposes ways for the
\member{container} field of the object to be modified. Note that
since no checks are made on the type of the object used to initialize
\member{container}, we have to assume that it may be a container.
\verbatiminput{cycle-gc.c}
Full details on the APIs related to the cycle detector are in
\ulink{Supporting Cyclic Garbarge
Collection}{../api/supporting-cycle-detection.html} in the
\citetitle[../api/api.html]{Python/C API Reference Manual}.
\subsection{More Suggestions}
Remember that you can omit most of these functions, in which case you

209
Doc/ext/noddy4.c Normal file
View File

@ -0,0 +1,209 @@
#include <Python.h>
#include "structmember.h"
typedef struct {
PyObject_HEAD
PyObject *first;
PyObject *last;
int number;
} Noddy;
static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
if (self->first && visit(self->first, arg) < 0)
return -1;
if (self->last && visit(self->last, arg) < 0)
return -1;
return 0;
}
static int
Noddy_clear(Noddy *self)
{
Py_XDECREF(self->first);
self->first = NULL;
Py_XDECREF(self->last);
self->last = NULL;
return 0;
}
static void
Noddy_dealloc(Noddy* self)
{
Noddy_clear(self);
self->ob_type->tp_free((PyObject*)self);
}
static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
Noddy *self;
self = (Noddy *)type->tp_alloc(type, 0);
if (self != NULL) {
self->first = PyString_FromString("");
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
self->last = PyString_FromString("");
if (self->last == NULL)
{
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *)self;
}
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
PyObject *first=NULL, *last=NULL;
static char *kwlist[] = {"first", "last", "number", NULL};
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_XDECREF(self->first);
Py_INCREF(first);
self->first = first;
}
if (last) {
Py_XDECREF(self->last);
Py_INCREF(last);
self->last = last;
}
return 0;
}
static PyMemberDef Noddy_members[] = {
{"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
"first name"},
{"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
"last name"},
{"number", T_INT, offsetof(Noddy, number), 0,
"noddy number"},
{NULL} /* Sentinel */
};
static PyObject *
Noddy_name(Noddy* self)
{
static PyObject *format = NULL;
PyObject *args, *result;
if (format == NULL) {
format = PyString_FromString("%s %s");
if (format == NULL)
return NULL;
}
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, "first");
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, "last");
return NULL;
}
args = Py_BuildValue("OO", self->first, self->last);
if (args == NULL)
return NULL;
result = PyString_Format(format, args);
Py_DECREF(args);
return result;
}
static PyMethodDef Noddy_methods[] = {
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
static PyTypeObject NoddyType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"noddy.Noddy", /*tp_name*/
sizeof(Noddy), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)Noddy_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
"Noddy objects", /* tp_doc */
(traverseproc)Noddy_traverse, /* tp_traverse */
(inquiry)Noddy_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Noddy_methods, /* tp_methods */
Noddy_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Noddy_init, /* tp_init */
0, /* tp_alloc */
Noddy_new, /* tp_new */
};
static PyMethodDef module_methods[] = {
{NULL} /* Sentinel */
};
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy4(void)
{
PyObject* m;
if (PyType_Ready(&NoddyType) < 0)
return;
m = Py_InitModule3("noddy4", module_methods,
"Example module that creates an extension type.");
if (m == NULL)
return;
Py_INCREF(&NoddyType);
PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

View File

@ -4,5 +4,6 @@ setup(name="noddy", version="1.0",
Extension("noddy", ["noddy.c"]),
Extension("noddy2", ["noddy2.c"]),
Extension("noddy3", ["noddy3.c"]),
Extension("noddy4", ["noddy4.c"]),
])

View File

@ -106,6 +106,99 @@ Traceback (most recent call last):
TypeError: an integer is required
>>> del n1
>>> del n2
Noddy 4
>>> import noddy4
>>> n1 = noddy4.Noddy('jim', 'fulton', 42)
>>> n1.first
'jim'
>>> n1.last
'fulton'
>>> n1.number
42
>>> n1.name()
'jim fulton'
>>> n1.first = 'will'
>>> n1.name()
'will fulton'
>>> n1.last = 'tell'
>>> n1.name()
'will tell'
>>> del n1.first
>>> n1.name()
Traceback (most recent call last):
...
AttributeError: first
>>> n1.first
Traceback (most recent call last):
...
AttributeError: first
>>> n1.first = 'drew'
>>> n1.first
'drew'
>>> del n1.number
Traceback (most recent call last):
...
TypeError: can't delete numeric/char attribute
>>> n1.number=2
>>> n1.number
2
>>> n1.first = 42
>>> n1.name()
'42 tell'
>>> n2 = noddy4.Noddy()
>>> n2 = noddy4.Noddy()
>>> n2 = noddy4.Noddy()
>>> n2 = noddy4.Noddy()
>>> n2.name()
' '
>>> n2.first
''
>>> n2.last
''
>>> del n2.first
>>> n2.first
Traceback (most recent call last):
...
AttributeError: first
>>> n2.first
Traceback (most recent call last):
...
AttributeError: first
>>> n2.name()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: first
>>> n2.number
0
>>> n3 = noddy4.Noddy('jim', 'fulton', 'waaa')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: an integer is required
Test cyclic gc(?)
>>> import gc
>>> gc.disable()
>>> x = []
>>> l = [x]
>>> n2.first = l
>>> n2.first
[[]]
>>> l.append(n2)
>>> del l
>>> del n1
>>> del n2
>>> sys.getrefcount(x)
3
>>> ignore = gc.collect()
>>> sys.getrefcount(x)
2
>>> gc.enable()
"""
import os