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:
parent
9c3e957251
commit
6c71091fbe
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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"]),
|
||||
])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue