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.