Skip to content

CPython API

Argument Parsing & Object Creation

These are the "entry and exit" gates for your C functions. They translate Python objects into C primitives and vice versa.

PyArg_ParseTuple

Used to extract values from Python positional arguments.

  • Format Strings: i (int), s (char), d (double), O (generic PyObject).
  • Example: PyArg_ParseTuple(args, "id", &my_int, &my_double);

Py_BuildValue

The inverse of parsing. It creates a Python object from C variables.

  • Example: return Py_BuildValue("i", 42); (Returns a Python integer).

Working with Numeric Types

Python numbers are objects, while C numbers are raw bits. You must convert them to perform math.

Function Purpose
PyLong_AsLong(obj) Converts a Python int to a C long.
PyLong_FromLong(val) Converts a C long to a Python int.
PyFloat_AsDouble(obj) Converts a Python float to a C double.
PyFloat_FromDouble(val) Converts a C double to a Python float.

Handling Sequences (Lists & Tuples)

When you receive a list from Python, you cannot iterate over it with a standard C for loop. You must use the API to "peek" inside.

PyList_Size(obj) / PyTuple_Size(obj)

Returns the length of the sequence. Essential for loop bounds.

PyList_GetItem(list, index)

Returns a borrowed reference to the item at that index.

Warning: "Borrowed" means you don't own the object; if the list is cleared, your pointer becomes invalid.

PySequence_Fast

If you want to support any iterable (lists, tuples, sets), use PySequence_Fast to convert it into a concrete array-like structure for high-speed access.


Error Handling

In C, you return NULL to signal an error, but you must also "set" the error message so Python can show a traceback.

  • PyErr_SetString(type, message): The most common way to raise an error.
  • Example: PyErr_SetString(PyExc_ValueError, "Index out of bounds");

  • PyErr_Occurred(): Checks if an error has already been set (useful when calling other API functions).


Common Format Unit Summary

When using PyArg_ParseTuple or Py_BuildValue, use these format units:

Unit C Type Description
s char * Null-terminated string.
i int Standard integer.
f float Single precision float.
d double Double precision float.
O PyObject * A raw Python object (no conversion).
p int Boolean (0 or 1).

Example

This code uses the Index-based Access method for maximum performance. It handles type checking, reference counting, and C-type conversion.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject* sum_sequence(PyObject* self, PyObject* args) {
    PyObject* input_seq;
    long total_sum = 0;

    // 1. Accept one object "O" from Python
    if (!PyArg_ParseTuple(args, "O", &input_seq)) {
        return NULL;
    }

    // 2. Determine if it's a List or a Tuple
    Py_ssize_t size;
    int is_list = PyList_Check(input_seq);
    int is_tuple = PyTuple_Check(input_seq);

    if (is_list) {
        size = PyList_Size(input_seq);
    } else if (is_tuple) {
        size = PyTuple_Size(input_seq);
    } else {
        PyErr_SetString(PyExc_TypeError, "Argument must be a list or a tuple.");
        return NULL;
    }

    // 3. Iterate through the sequence
    for (Py_ssize_t i = 0; i < size; i++) {
        PyObject* item;

        // Get the item (Borrowed reference - no DECREF needed)
        if (is_list) {
            item = PyList_GetItem(input_seq, i);
        } else {
            item = PyTuple_GetItem(input_seq, i);
        }

        // 4. Convert Python Object to C Long
        if (PyLong_Check(item)) {
            total_sum += PyLong_AsLong(item);
        } else {
            PyErr_Format(PyExc_TypeError, "Element at index %zd is not an integer.", i);
            return NULL;
        }
    }

    // 5. Convert C result back to Python Object
    return PyLong_FromLong(total_sum);
}

This function, process_dict, accepts a dictionary, iterates through its keys and values, and prints them to the console (or processes them).

#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject* process_dict(PyObject* self, PyObject* args) {
    PyObject* input_dict;

    // 1. Accept one object "O"
    if (!PyArg_ParseTuple(args, "O", &input_dict)) {
        return NULL;
    }

    // 2. Type Check: Ensure it is actually a dictionary
    if (!PyDict_Check(input_dict)) {
        PyErr_SetString(PyExc_TypeError, "Argument must be a dictionary.");
        return NULL;
    }

    PyObject *key, *value;
    Py_ssize_t pos = 0;

    // 3. Iterate using PyDict_Next
    // 'pos' must be initialized to 0 and is updated by the function
    while (PyDict_Next(input_dict, &pos, &key, &value)) {

        // key and value are BORROWED references. 
        // Do not Py_DECREF them.

        // Example: If keys are strings and values are integers
        if (PyUnicode_Check(key) && PyLong_Check(value)) {
            const char* key_str = PyUnicode_AsUTF8(key);
            long val_int = PyLong_AsLong(value);

            printf("Key: %s | Value: %ld\n", key_str, val_int);
        }
    }

    // Return None to Python
    Py_RETURN_NONE;
}

Key Mechanics of PyDict_Next

Unlike list iteration, dictionary iteration requires a position tracker (Py_ssize_t pos).

Component Description
&pos An integer pointer. PyDict_Next uses this to track its progress through the internal hash table. Always initialize to 0.
&key A pointer to a PyObject*. It will be populated with the current key.
&value A pointer to a PyObject*. It will be populated with the current value.
Reference Both key and value are Borrowed References.

Dictionary Mutation

You must not modify the dictionary (add or delete keys) while iterating through it using PyDict_Next. Doing so can change the internal hash table layout, leading to crashes or undefined behavior.

  • PyDict_Keys(dict): Returns a new List of all keys.
  • PyDict_Values(dict): Returns a new List of all values.
  • PyDict_Items(dict): Returns a new List of (key, value) tuples.