A full and minimal example for a class (not method) with Python C Extension?

2024/10/4 11:24:53

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:

  1. https://docs.python.org/3/c-api/
  2. https://docs.python.org/3/extending/extending.html
Answer

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:

  1. https://docs.python.org/3/extending/newtypes_tutorial.html#supporting-cyclic-garbage-collection
https://en.xdnf.cn/q/70614.html

Related Q&A

Python: Grouping into timeslots (minutes) for days of data

I have a list of events that occur at mS accurate intervals, that spans a few days. I want to cluster all the events that occur in a per-n-minutes slot (can be twenty events, can be no events). I have …

signal.alarm not triggering exception on time

Ive slightly modified the signal example from the official docs (bottom of page).Im calling sleep 10 but I would like an alarm to be raised after 1 second. When I run the following snippet it takes way…

Execute Python (selenium) script in crontab

I have read most of the python/cron here in stackoverflow and yet couldnt make my script run. I understood that I need to run my script through shell (using zsh & ipython by the way), but really I …

Get post data from ajax post request in python file

Im trying to post some data with an ajax post request and execute a python file, retrieving the data in the python file, and return a result.I have the following ajax code$(function () {$("#upload…

How to implement maclaurin series in keras?

I am trying to implement expandable CNN by using maclaurin series. The basic idea is the first input node can be decomposed into multiple nodes with different orders and coefficients. Decomposing singl…

Rowwise min() and max() fails for column with NaNs

I am trying to take the rowwise max (and min) of two columns containing datesfrom datetime import date import pandas as pd import numpy as np df = pd.DataFrame({date_a : [date(2015, 1, 1), date(2012…

Convert column suffixes from pandas join into a MultiIndex

I have two pandas DataFrames with (not necessarily) identical index and column names. >>> df_L = pd.DataFrame({X: [1, 3], Y: [5, 7]})>>> df_R = pd.DataFrame({X: [2, 4], Y: [6, 8]})I c…

sys-package-mgr*: cant create package cache dir when run python script with Jython

I want to run Python script with Jython. the result show correctly, but at the same time there is an warning message, "sys-package-mgr*: cant create package cache dir"How could I solve this p…

Python WWW macro

i need something like iMacros for Python. It would be great to have something like that:browse_to(www.google.com) type_in_input(search, query) click_button(search) list = get_all(<p>)Do you know …

Django custom context_processors in render_to_string method

Im building a function to send email and I need to use a context_processor variable inside the HTML template of the email, but this dont work.Example:def send_email(plain_body_template_name, html_body_…