Everywhere, I can easily find an example about writing a method with Python C Extensions and use it in Python. Like this one: Python 3 extension example
$ python3
>>> import hello
>>> hello.hello_world()
Hello, world!
>>> hello.hello('world')
Hello, world!
How to do write a hello word full featured Python class (not just a module method)?
I think this How to wrap a C++ object using pure Python Extension API (python3)? question has an example, but it does not seem minimal as he is using (or wrapping?) C++ classes on it.
For example:
class ClassName(object):"""docstring for ClassName"""def __init__(self, hello):super().__init__()self.hello = hellodef talk(self, world):print( '%s %s' % ( self.hello, world ) )
What is the equivalent of this Python class example with C Extensions?
I would use it like this:
from .mycextensionsmodule import ClassNameclassname = ClassName("Hello")
classname.talk( 'world!' )
# prints "Hello world!"
My goal is to write a class fully written in C for performance (all other classes in my project will be in Python, except this one). I am not looking for portability as using ctypes, neither black boxes as using Boost.Python or SWIG. Just a high-performance class purely written with Python C Extensions.
After I got this Hello word
working, I can figure my self out within Python Extensive documentation:
- https://docs.python.org/3/c-api/
- https://docs.python.org/3/extending/extending.html
See also: Python instance method in C
Create the file called MANIFEST.in
include README.md
include LICENSE.txtrecursive-include source *.h
Create the file called setup.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from setuptools import setup, Extension__version__ = '0.1.0'setup(name = 'custom',version = __version__,package_data = {'': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],},ext_modules = [Extension(name = 'custom',sources = ['source/custom.cpp',],include_dirs = ['source'],)],)
Create the file called source/custom.cpp
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"typedef struct {PyObject_HEADPyObject *first; /* first name */PyObject *last; /* last name */int number;
} CustomObject;static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{Py_VISIT(self->first);Py_VISIT(self->last);return 0;
}static int
Custom_clear(CustomObject *self)
{Py_CLEAR(self->first);Py_CLEAR(self->last);return 0;
}static void
Custom_dealloc(CustomObject *self)
{PyObject_GC_UnTrack(self);Custom_clear(self);Py_TYPE(self)->tp_free((PyObject *) self);
}static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{CustomObject *self;self = (CustomObject *) type->tp_alloc(type, 0);if (self != NULL) {self->first = PyUnicode_FromString("");if (self->first == NULL) {Py_DECREF(self);return NULL;}self->last = PyUnicode_FromString("");if (self->last == NULL) {Py_DECREF(self);return NULL;}self->number = 0;}return (PyObject *) self;
}static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{static char *kwlist[] = {"first", "last", "number", NULL};PyObject *first = NULL, *last = NULL, *tmp;if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,&first, &last,&self->number))return -1;if (first) {tmp = self->first;Py_INCREF(first);self->first = first;Py_DECREF(tmp);}if (last) {tmp = self->last;Py_INCREF(last);self->last = last;Py_DECREF(tmp);}return 0;
}static PyMemberDef Custom_members[] = {{"number", T_INT, offsetof(CustomObject, number), 0,"custom number"},{NULL} /* Sentinel */
};static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{Py_INCREF(self->first);return self->first;
}static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{if (value == NULL) {PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");return -1;}if (!PyUnicode_Check(value)) {PyErr_SetString(PyExc_TypeError,"The first attribute value must be a string");return -1;}Py_INCREF(value);Py_CLEAR(self->first);self->first = value;return 0;
}static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{Py_INCREF(self->last);return self->last;
}static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{if (value == NULL) {PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");return -1;}if (!PyUnicode_Check(value)) {PyErr_SetString(PyExc_TypeError,"The last attribute value must be a string");return -1;}Py_INCREF(value);Py_CLEAR(self->last);self->last = value;return 0;
}static PyGetSetDef Custom_getsetters[] = {{"first", (getter) Custom_getfirst, (setter) Custom_setfirst,"first name", NULL},{"last", (getter) Custom_getlast, (setter) Custom_setlast,"last name", NULL},{NULL} /* Sentinel */
};static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{return PyUnicode_FromFormat("%S %S", self->first, self->last);
}static PyMethodDef Custom_methods[] = {{"name", (PyCFunction) Custom_name, METH_NOARGS,"Return the name, combining the first and last name"},{NULL} /* Sentinel */
};static PyTypeObject CustomType = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "custom.Custom",.tp_doc = "Custom objects",.tp_basicsize = sizeof(CustomObject),.tp_itemsize = 0,.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,.tp_new = Custom_new,.tp_init = (initproc) Custom_init,.tp_dealloc = (destructor) Custom_dealloc,.tp_traverse = (traverseproc) Custom_traverse,.tp_clear = (inquiry) Custom_clear,.tp_members = Custom_members,.tp_methods = Custom_methods,.tp_getset = Custom_getsetters,
};static PyModuleDef custommodule = {PyModuleDef_HEAD_INIT,.m_name = "custom",.m_doc = "Example module that creates an extension type.",.m_size = -1,
};PyMODINIT_FUNC
PyInit_custom(void)
{PyObject *m;if (PyType_Ready(&CustomType) < 0)return NULL;m = PyModule_Create(&custommodule);if (m == NULL)return NULL;Py_INCREF(&CustomType);PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);return m;
}
Then, to compile it and install you can run either:
pip3 install . -v
python3 setup.py install
As side note from this question How to use setuptools packages and ext_modules with the same name? do not mix on the same project *.py
files and Python C Extensions, i.e., use only purely C/C++, building Python C Extensions without adding packages = [ 'package_name' ]
entries because they cause the Python C Extensions code run 30%, i.e., if the program would take 7 seconds to run, now with *.py
files, it will take 11 seconds.
References:
- https://docs.python.org/3/extending/newtypes_tutorial.html#supporting-cyclic-garbage-collection