A number of these abstract interfaces were defined early in the
development of the Python implementation. In particular, the number,
mapping, and sequence protocols have been part of Python since the
beginning. Other protocols have been added over time. For protocols
which depend on several handler routines from the type implementation,
the older protocols have been defined as optional blocks of handlers
referenced by the type object, while newer protocols have been added
using additional slots in the main type object, with a flag bit being
set to indicate that the slots are present. (The flag bit does not
indicate that the slot values are non-NULL.)
If you wish your object to be able to act like a number, a sequence,
or a mapping object, then you place the address of a structure that
implements the C type PyNumberMethods,
PySequenceMethods, or PyMappingMethods, respectively.
It is up to you to fill in this structure with appropriate values. You
can find examples of the use of each of these in the Objects
directory of the Python source distribution.
hashfunc tp_hash;
This function, if you choose to provide it, should return a hash
number for an instance of your datatype. Here is a moderately
pointless example:
static long
newdatatype_hash(newdatatypeobject *obj)
{
long result;
result = obj->obj_UnderlyingDatatypePtr->size;
result = result * 3;
return result;
}
ternaryfunc tp_call;
This function is called when an instance of your datatype is "called",
for example, if obj1 is an instance of your datatype and the Python
script contains obj1('hello'), the tp_call handler is
invoked.
This function takes three arguments:
arg1 is the instance of the datatype which is the subject of
the call. If the call is obj1('hello'), then arg1 is
obj1.
arg2 is a tuple containing the arguments to the call. You
can use PyArg_ParseTuple() to extract the arguments.
arg3 is a dictionary of keyword arguments that were passed.
If this is non-NULL and you support keyword arguments, use
PyArg_ParseTupleAndKeywords() to extract the
arguments. If you do not want to support keyword arguments and
this is non-NULL, raise a TypeError with a message
saying that keyword arguments are not supported.
Here is a desultory example of the implementation of the call function.
/* Implement the call function.
* obj1 is the instance receiving the call.
* obj2 is a tuple containing the arguments to the call, in this
* case 3 strings.
*/
static PyObject *
newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)
{
PyObject *result;
char *arg1;
char *arg2;
char *arg3;
if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
return NULL;
}
result = PyString_FromFormat(
"Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",
obj->obj_UnderlyingDatatypePtr->size,
arg1, arg2, arg3);
printf("\%s", PyString_AS_STRING(result));
return result;
}
These functions provide support for the iterator protocol. Any object
which wishes to support iteration over it's contents (which may be
generated during iteration) must implement the tp_iter
handler. Objects which are returned by a tp_iter handler must
implement both the tp_iter and tp_iternext handlers.
Both handlers take exactly one parameter, the instance for which they
are being called, and return a new reference. In the case of an
error, they should set an exception and return NULL.
For an object which represents an iterable collection, the
tp_iter handler must return an iterator object. The iterator
object is responsible for maintaining the state of the iteration. For
collections which can support multiple iterators which do not
interfere with each other (as lists and tuples do), a new iterator
should be created and returned. Objects which can only be iterated
over once (usually due to side effects of iteration) should implement
this handler by returning a new reference to themselves, and should
also implement the tp_iternext handler. File objects are an
example of such an iterator.
Iterator objects should implement both handlers. The tp_iter
handler should return a new reference to the iterator (this is the
same as the tp_iter handler for objects which can only be
iterated over destructively). The tp_iternext handler should
return a new reference to the next object in the iteration if there is
one. If the iteration has reached the end, it may return NULL
without setting an exception or it may set StopIteration;
avoiding the exception can yield slightly better performance. If an
actual error occurs, it should set an exception and return NULL.