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.
diff --git a/Doc/ext/cycle-gc.c b/Doc/ext/cycle-gc.c
deleted file mode 100644
index c3a0caa..0000000
--- a/Doc/ext/cycle-gc.c
+++ /dev/null
@@ -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;
-}
diff --git a/Doc/ext/newtypes.tex b/Doc/ext/newtypes.tex
index d834ccc..df78d54 100644
--- a/Doc/ext/newtypes.tex
+++ b/Doc/ext/newtypes.tex
@@ -670,6 +670,97 @@
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 @@
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
diff --git a/Doc/ext/noddy4.c b/Doc/ext/noddy4.c
new file mode 100644
index 0000000..078666a
--- /dev/null
+++ b/Doc/ext/noddy4.c
@@ -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);
+}
diff --git a/Doc/ext/setup.py b/Doc/ext/setup.py
index 5b99cfe..1805b17 100644
--- a/Doc/ext/setup.py
+++ b/Doc/ext/setup.py
@@ -4,5 +4,6 @@
Extension("noddy", ["noddy.c"]),
Extension("noddy2", ["noddy2.c"]),
Extension("noddy3", ["noddy3.c"]),
+ Extension("noddy4", ["noddy4.c"]),
])
diff --git a/Doc/ext/test.py b/Doc/ext/test.py
index 5e09e7c..10549d6 100644
--- a/Doc/ext/test.py
+++ b/Doc/ext/test.py
@@ -106,6 +106,99 @@
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