Python's C-API: Extending Python with C or C++

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setting Up
  4. Overview of Python’s C-API
  5. Creating a Simple C Extension
  6. Passing Arguments between Python and C
  7. Handling Exceptions
  8. Using C++ with Python
  9. Conclusion

Introduction

Welcome to the tutorial on Python’s C-API! In this tutorial, we will explore how to extend Python by writing C or C++ extensions. By the end of this tutorial, you will understand the basics of Python’s C-API and be able to create your own C or C++ extensions to enhance Python’s functionalities.

Prerequisites

Before starting this tutorial, you should have a good understanding of the Python programming language, including basic syntax and concepts. Knowledge of C or C++ is also beneficial, although not mandatory.

Setting Up

To follow along with this tutorial, you need to have Python installed on your machine. You can download the latest stable version of Python from the official website (https://www.python.org/downloads/). Additionally, you will need a C compiler such as GCC or Clang, depending on your operating system.

Overview of Python’s C-API

Python’s C-API is a set of functions and macros provided by the Python interpreter to allow us to write C or C++ code that interacts with Python objects and the Python runtime. It provides a way to extend Python by creating native C or C++ modules.

The C-API allows us to:

  • Create new Python objects
  • Call Python functions from C
  • Manipulate Python objects and their attributes
  • Access and modify Python data structures
  • Handle exceptions raised from Python code
  • And much more…

Creating a Simple C Extension

In this section, we will create a simple C extension to generate Fibonacci numbers. This will serve as a basic example to demonstrate the usage of Python’s C-API.

  1. Create a new C source file named fibonacci.c.

  2. Open the fibonacci.c file in a text editor or an integrated development environment (IDE).

  3. Include the necessary headers:
     #include <Python.h>
    
  4. Define the fibonacci function in C:
     long fibonacci(long n) {
         if (n <= 0) {
             return 0;
         } else if (n == 1) {
             return 1;
         } else {
             return fibonacci(n - 1) + fibonacci(n - 2);
         }
     }
    
  5. Define the wrapping function to expose the fibonacci function to Python:
     static PyObject* fibonacci_wrapper(PyObject* self, PyObject* args) {
         long n;
         if (!PyArg_ParseTuple(args, "l", &n)) {
             return NULL;
         }
         long result = fibonacci(n);
         return PyLong_FromLong(result);
     }
    
  6. Define a method table to map Python function names to C functions:
     static PyMethodDef module_methods[] = {
         {"fibonacci", fibonacci_wrapper, METH_VARARGS, "Calculate the Fibonacci number."},
         {NULL, NULL, 0, NULL}
     };
    
  7. Define the module initialization function:
     static struct PyModuleDef fibonacci_module = {
         PyModuleDef_HEAD_INIT,
         "fibonacci",
         "A simple module to calculate Fibonacci numbers.",
         -1,
         module_methods
     };
    	
     PyMODINIT_FUNC PyInit_fibonacci(void) {
         return PyModule_Create(&fibonacci_module);
     }
    
  8. Save the fibonacci.c file.

  9. Compile the C source code into a shared library (e.g., fibonacci.so):
     $ gcc -shared -o fibonacci.so fibonacci.c -I/usr/include/python3.9
    
  10. Now, you can use the fibonacci module in Python:
    import fibonacci
    	
    print(fibonacci.fibonacci(10))  # Output: 55
    

    Congratulations! You have successfully created and used a basic C extension in Python.

Passing Arguments between Python and C

In the previous example, we passed a single argument from Python to C. However, the C-API provides various functions and macros to handle different argument types and conversions.

Passing Numeric Arguments

To pass numeric arguments between Python and C, you can use the appropriate PyArg_Parse* functions:

  • PyArg_ParseTuple: Parses a tuple of arguments.
  • PyArg_ParseTupleAndKeywords: Parses a tuple of arguments and keyword arguments.
  • PyArg_Parse: Parses a single argument.
  • PyArg_ParseTupleAndKeywords: Parses a single argument and keyword arguments.

Here’s an example of passing integer and float arguments from Python to C: c static PyObject* sum_wrapper(PyObject* self, PyObject* args) { int a; float b; if (!PyArg_ParseTuple(args, "if", &a, &b)) { return NULL; } float result = a + b; return PyFloat_FromDouble(result); } In the above example, PyArg_ParseTuple(args, "if", &a, &b) parses a tuple of arguments and extracts an integer and a float, storing them in a and b, respectively.

Returning Values to Python

To return values from C to Python, you can use various functions, such as Py_BuildValue, PyLong_FromLong, PyFloat_FromDouble, and more. These functions allow you to create Python objects from C data types.

Here’s an example of returning multiple values from C to Python: c static PyObject* multiple_values(PyObject* self, PyObject* args) { long a = 10; float b = 3.14; PyObject* c = PyUnicode_FromString("hello"); return Py_BuildValue("lfO", a, b, c); } In the above example, Py_BuildValue("lfO", a, b, c) builds a tuple with a long, a float, and a Python object, respectively.

Handling Exceptions

Handling exceptions is an important part of writing robust Python extensions. The C-API provides functions and macros to raise and handle exceptions.

Raising Exceptions

To raise exceptions from C, you can use the PyErr_SetString function, which takes an exception type and an error message. c if (error_condition) { PyErr_SetString(PyExc_RuntimeError, "Error occurred."); return NULL; } The PyExc_RuntimeError is just one example of the built-in exception types provided by the C-API. There are many other exception types available, such as PyExc_TypeError, PyExc_ValueError, and more.

Checking for Exceptions

To check if an exception has been raised in Python, you can use the PyErr_Occurred function, which returns a non-zero value if an exception is pending. You can also use the PyErr_ExceptionMatches function to check if the raised exception matches a specific exception type. c if (PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_RuntimeError)) { // Handle Runtime error } else if (PyErr_ExceptionMatches(PyExc_TypeError)) { // Handle Type error } }

Clearing Exceptions

To clear a pending exception, you can use the PyErr_Clear function. c PyErr_Clear();

Using C++ with Python

Python’s C-API can also be used with C++ to create Python extensions. The main difference is that you need to ensure the C++ compiler is used instead of the C compiler when building the extension.

To use C++ with Python:

  1. Create a new C++ source file (e.g., example.cpp).

  2. Include the necessary headers:
     #include <Python.h>
    
  3. Define the C++ functions:
     long fibonacci(long n) {
         if (n <= 0) {
             return 0;
         } else if (n == 1) {
             return 1;
         } else {
             return fibonacci(n - 1) + fibonacci(n - 2);
         }
     }
    
  4. Wrap the C++ functions similar to what we did in the C example:
     static PyObject* fibonacci_wrapper(PyObject* self, PyObject* args) {
         long n;
         if (!PyArg_ParseTuple(args, "l", &n)) {
             return NULL;
         }
         long result = fibonacci(n);
         return PyLong_FromLong(result);
     }
    
  5. Define the method table, module initialization function, and module definition just like before.

  6. Save the example.cpp file.

  7. Compile the C++ source code into a shared library:
     $ g++ -shared -o example.so example.cpp -I/usr/include/python3.9
    
  8. Now, you can use the example module in Python:
     import example
    	
     print(example.fibonacci(10))  # Output: 55
    

    Using C++ with Python allows you to leverage the features and benefits of both languages and create powerful extensions.

Conclusion

In this tutorial, we explored Python’s C-API and learned how to extend Python by writing C or C++ extensions. We covered the basics of creating a simple C extension, passing arguments between Python and C, handling exceptions, and even using C++ with Python.

You should now have a good understanding of Python’s C-API and be able to create your own C or C++ extensions to enhance Python’s functionalities. Keep exploring and experimenting to unlock the full potential of Python and C/C++ integration.

Remember to refer to the official Python C-API documentation for more detailed information and advanced topics. Happy coding!