L
Lawrence D'Oliveiro
I wrote my first Python extension library over the last couple of weeks. I
took note of all the recommendations to keep track of reference counts, to
ensure that objects were not disposed when they shouldn’t be, and were when
they should. However, the example code seems to use gotos. And the trouble
with these is that they don’t nest and un-nest easily; try to do too much
refactoring with them, and you run into the age-old “spaghetti codeâ€
problem.
Which is where the do-once block comes in. The basic control flow is this:
* Unconditionally initialize all dynamic storage to nil
* Do the main body of the code, aborting in any error
* Regardless of success or failure of the above, dispose of all
allocated dynamic storage, using disposal calls which turn into
noops if passed pointers that are already nil.
For example, here’s a utility routine from my extension that, passed a
Python array object, returns the address and length of the storage:
static void GetBufferInfo
(
PyObject * FromArray,
unsigned long * addr,
unsigned long * len
)
/* returns the address and length of the data in a Python array object. */
{
PyObject * TheBufferInfo = 0;
PyObject * AddrObj = 0;
PyObject * LenObj = 0;
do /*once*/
{
TheBufferInfo = PyObject_CallMethod(FromArray, "buffer_info", "");
if (TheBufferInfo == 0)
break;
AddrObj = PyTuple_GetItem(TheBufferInfo, 0);
LenObj = PyTuple_GetItem(TheBufferInfo, 1);
if (PyErr_Occurred())
break;
Py_INCREF(AddrObj);
Py_INCREF(LenObj);
*addr = PyInt_AsUnsignedLongMask(AddrObj);
*len = PyInt_AsUnsignedLongMask(LenObj);
if (PyErr_Occurred())
break;
}
while (false);
Py_XDECREF(AddrObj);
Py_XDECREF(LenObj);
Py_XDECREF(TheBufferInfo);
} /*GetBufferInfo*/
You can pretty much determine by inspection that all the reference counts
are properly maintained, no need to trace through convoluted flows of
control.
took note of all the recommendations to keep track of reference counts, to
ensure that objects were not disposed when they shouldn’t be, and were when
they should. However, the example code seems to use gotos. And the trouble
with these is that they don’t nest and un-nest easily; try to do too much
refactoring with them, and you run into the age-old “spaghetti codeâ€
problem.
Which is where the do-once block comes in. The basic control flow is this:
* Unconditionally initialize all dynamic storage to nil
* Do the main body of the code, aborting in any error
* Regardless of success or failure of the above, dispose of all
allocated dynamic storage, using disposal calls which turn into
noops if passed pointers that are already nil.
For example, here’s a utility routine from my extension that, passed a
Python array object, returns the address and length of the storage:
static void GetBufferInfo
(
PyObject * FromArray,
unsigned long * addr,
unsigned long * len
)
/* returns the address and length of the data in a Python array object. */
{
PyObject * TheBufferInfo = 0;
PyObject * AddrObj = 0;
PyObject * LenObj = 0;
do /*once*/
{
TheBufferInfo = PyObject_CallMethod(FromArray, "buffer_info", "");
if (TheBufferInfo == 0)
break;
AddrObj = PyTuple_GetItem(TheBufferInfo, 0);
LenObj = PyTuple_GetItem(TheBufferInfo, 1);
if (PyErr_Occurred())
break;
Py_INCREF(AddrObj);
Py_INCREF(LenObj);
*addr = PyInt_AsUnsignedLongMask(AddrObj);
*len = PyInt_AsUnsignedLongMask(LenObj);
if (PyErr_Occurred())
break;
}
while (false);
Py_XDECREF(AddrObj);
Py_XDECREF(LenObj);
Py_XDECREF(TheBufferInfo);
} /*GetBufferInfo*/
You can pretty much determine by inspection that all the reference counts
are properly maintained, no need to trace through convoluted flows of
control.