Monday, June 1, 2009

datetime64 Objects

I love having working code. Once I can get something to properly work at the most basic level, I slowly modify it from the ground up. The Python C API does not make this easy. In order to create even the most basic Python Object from a C module, you need to be acquainted with a host of obscure and often arcane code segments. I'll try to piece together the basic datetime64 object here.

First, as always, include the Python C API
#include <Python.h>

I'm a little confused about this line, but I think it just tells the compiler how to interpret "datetime64Type" as a PyTypeObject.
staticforward PyTypeObject datetime64Type;
The following is the actual datetime64 object, itself. We have a simple struct filled the PyObject_HEAD (a macro to put in the reference to the object's location, I think), freq (to tell us the frequency time refers to), and time (number of freq since the epoch, granted I use Unix Time).

typedef struct
{
PyObject_HEAD // macro used for refcount & pointer
int freq; // frequency of date_value
long long time; // 64 bit time since epoch
} datetime64;

We need to deallocate datetime64 objects. Later, we tell Python to use this function for just that.
static void
datetime64_dealloc(PyObject* self)
{
PyObject_Del(self);
}

Here we give Python a bunch of information about the datetime64 Object Types. We tell it the size of the object, the name, what to run when it's deallocated, the documentation, and other (sometimes irrelevant and no longer used) information.

static PyTypeObject datetime64Type = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"datetime64.datetime64", /*tp_name*/
sizeof(datetime64), /*tp_basicsize*/
0, /*tp_itemsize*/
datetime64_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, /*tp_flags*/
"datetime64 objects", /* tp_doc */
};

The PyMethodDef is an array of every method we can use on objects of datetime64. We put in the name of the method, the function to call, the METH_VARARGS alias, and a description of the method. We could put (for example) "add" as an entry so that we can perform the operation "object.add()". This tells Python where to go when we call each method. The NULL references at the end are a sentinel for Python to know when we're done referencing methods.
PyMethodDef datetime64_methods[] = {
{NULL, NULL, 0, NULL}};

Here's the big, important part of the code. We use the PyMODINIT_FUNC preprocessor directive to tell Python that this is our initialization function. Python will run this when we initialize objects of datetime64.

PyMODINIT_FUNC
initdatetime64(void)
{
Here we create the date_object using a PyObject.
PyObject *date_object;initdatetime64(void)
{
PyObject *date_object;

Since we're not really doing anything fancy with our datetime64 objects yet, we use PyType_GenericNew to make a generic Python Object and store it under our datetime64Type.tp_new variable. Remember, the datetime64Type tells Python what kind of object a datetime64 object is. When we make the tp_new a generic type, we don't tell it much, but we at least set a type for it.
datetime64Type.tp_new = PyType_GenericNew;
These lines will initialize the datetime64 object, and make sure it's a legit object. We'll return, otherwise.

if (PyType_Ready(&datetime64Type) < 0)
return;

These next lines are possibly the most important method. Py_InitModule3 will create a new module object based on a name and table of functions. We give it "datetime64" to tell it the name, and the datetime64_methods array to tell Python what methods we can run on it.

date_object = Py_InitModule3("datetime64", datetime64_methods,
"DateTime64 module that creates a DateTime64 Object");

Tell Python to increase the reference count for this type.
Py_INCREF(&datetime64Type);
Add the datetime64Type to Python's module dictionary.
PyModule_AddObject(date_object, "datetime64", (PyObject *)&datetime64Type);
}
There you have it. Let's run the build and install (install so I don't have to go looking for the .so file the setup.py file creates) and see if it worked.
>>> import datetime64 as d
>>> day = d.datetime64()
>>> day
<datetime64.datetime64 object at 0x7f20d28350f0>

Looks like a Python Object to me! Since we didn't give Python any methods to run on it, and since the initialization of the object doesn't actually give the object any parameters to set, we can't do a whole lot with it... But hey! We can make them, right? I'll be defining Unit Tests in the next day or so and posting them here, so keep an eye out.

Next up for the day, timedelta64!

No comments:

Post a Comment