You can choose the right calling convention for a function, parse arguments without creating dangling pointers, build return values, and invoke Python callables from C using the modern low-overhead APIs.
PyMethodDef: the table that exposes C to Python
Whether a C function becomes a module-level function or a method on a type, it is registered through the same struct:
typedef struct PyMethodDef {
const char *ml_name; /* the Python-visible name */
PyCFunction ml_meth; /* the C function pointer */
int ml_flags; /* calling convention */
const char *ml_doc; /* the docstring */
} PyMethodDef;
Arrays of these are NULL-terminated and are assigned to m_methods on a module or tp_methods/Py_tp_methods on a type. The first parameter of every C function in the table is conventionally called self, and it is:
- the module object, for a function in
m_methods; - the instance, for a method in
tp_methods; - the type, if
METH_CLASSis set; NULL, ifMETH_STATICis set.
This is worth pausing on: for a module-level function, self is your module, which is how you reach per-module state in Lesson 08 without any globals.
Calling conventions
| Flags | C signature | Use for |
|---|---|---|
METH_NOARGS |
PyObject *f(PyObject *self, PyObject *unused) |
Zero-argument functions. The second parameter is always NULL; name it Py_UNUSED(ignored). |
METH_O |
PyObject *f(PyObject *self, PyObject *arg) |
Exactly one positional argument. No parsing at all — the fastest possible entry point. |
METH_VARARGS |
PyObject *f(PyObject *self, PyObject *args) |
Arguments arrive as a tuple. The classic convention. Requires building a tuple on every call. |
METH_VARARGS | METH_KEYWORDS |
PyObject *f(PyObject *self, PyObject *args, PyObject *kwargs) |
Tuple plus dict (which may be NULL). The general-purpose choice. |
METH_FASTCALL |
PyObject *f(PyObject *self, PyObject *const *args, Py_ssize_t nargs) |
Positional arguments as a C array. No tuple is allocated. Preferred for hot paths. |
METH_FASTCALL | METH_KEYWORDS |
PyObject *f(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) |
Array of positionals followed by keyword values; kwnames is a tuple of the corresponding names, or NULL. |
METH_METHOD | METH_FASTCALL | METH_KEYWORDS |
PyObject *f(PyObject *self, PyTypeObject *defining_class, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) |
Adds the class that defined the method, which is how a method on a heap type reaches module state (Lesson 08). |
Modifier flags that combine with the above: METH_CLASS (classmethod), METH_STATIC (staticmethod), METH_COEXIST (allow this method to take precedence over a slot-generated wrapper of the same name).
The METH_FASTCALL layout — a pointer to a contiguous array of arguments plus a count plus an optional names tuple — is CPython's vectorcall protocol (PEP 590). The interpreter already has arguments laid out contiguously on its value stack, so passing a pointer into that stack costs nothing, whereas METH_VARARGS forces it to allocate a tuple, copy pointers in, and free it again on every call. For a function called in a loop this is a measurable fraction of the total cost. There is a matching consumer side, PyObject_Vectorcall, for calling out.
Caveat: the arguments in a METH_FASTCALL array are borrowed and the array itself is only valid for the duration of the call. Do not store either.
Choosing
- One argument, no keywords →
METH_O. - No arguments →
METH_NOARGS. - A few positional arguments on a hot path →
METH_FASTCALL. - Anything with keyword arguments and a complex signature →
METH_VARARGS | METH_KEYWORDSwithPyArg_ParseTupleAndKeywords, unless you are using Argument Clinic (below), in which case take the fastcall version it generates.
PyArg_ParseTuple and friends
The parsing functions take a format string describing the expected arguments and a list of pointers to fill in. They return true on success, and on failure they return false with a suitable TypeError or ValueError already raised — so the caller just returns NULL.
static PyObject *
resize(PyObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {"width", "height", "mode", NULL};
int width, height;
const char *mode = "nearest"; /* default for the optional argument */
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|s:resize", kwlist,
&width, &height, &mode)) {
return NULL;
}
...
}
The kwlist array must have exactly one entry per format unit, in order, terminated by NULL. An empty string entry marks a positional-only parameter.
Format-string markers
| | Everything after this is optional. Your C variables keep whatever value you initialized them to — so initialize them, since the parser does not touch them when the argument is absent. |
$ | Everything after this is keyword-only. ParseTupleAndKeywords only. |
: | Ends the format; the rest is the function name used in error messages. |
; | Ends the format; the rest replaces the entire error message. |
The format units you will actually use
| Unit | C type | Accepts | Lifetime |
|---|---|---|---|
i l L n K | int, long, long long, Py_ssize_t, unsigned long long | int (via __index__) | By value — safe. |
f d | float, double | float | By value — safe. |
p | int | anything; result is 0/1 truthiness | By value — safe. |
s | const char * | str, encoded UTF-8, must contain no NUL | Borrowed into the str's internal cache. |
s# | const char *, Py_ssize_t | str or read-only bytes-like; NULs allowed | Borrowed. |
z, z# | as s | same, plus None → NULL pointer | Borrowed. |
y, y# | const char * (+len) | read-only bytes-like, no str | Borrowed. |
s*, y*, w* | Py_buffer | any buffer-exporting object (w* requires writable) | Locked view — you must call PyBuffer_Release. |
U | PyObject * | str only, no conversion | Borrowed reference. |
O | PyObject * | anything | Borrowed reference. |
O! | PyTypeObject *, PyObject * | instances of that type (subclasses allowed) | Borrowed reference. |
O& | converter fn, void * | whatever your converter accepts | Whatever your converter defines. |
Everything the parser hands you that is a pointer — s, y, z, O, U — is borrowed, kept alive only by the argument tuple, which is kept alive only for the duration of your call. Three consequences:
- Never store one of those pointers past the return of your function. Copy the bytes, or incref the object.
- Never use a
const char *froms/yafter releasing the interpreter (Lesson 10) — the owningstrcould be collected. Uses*/y*, which pins the memory, for anything you want to touch while detached. - A
Py_bufferfroms*/y*/w*must be released on every exit path, including error paths.
Custom converters with O&
O& calls a function of yours to turn the object into whatever C value you want, which keeps validation out of the function body:
/* Returns 1 on success, 0 on failure with an exception set.
Return Py_CLEANUP_SUPPORTED instead of 1 to opt into cleanup calls. */
static int
convert_mode(PyObject *obj, void *addr)
{
const char *s = PyUnicode_AsUTF8(obj);
if (s == NULL) return 0;
if (strcmp(s, "fast") == 0) { *(int *)addr = MODE_FAST; return 1; }
if (strcmp(s, "accurate") == 0) { *(int *)addr = MODE_ACC; return 1; }
PyErr_Format(PyExc_ValueError, "unknown mode %R", obj);
return 0;
}
/* usage: */
int mode = MODE_FAST;
PyArg_ParseTuple(args, "O&:go", convert_mode, &mode);
The lightweight alternative
When you only need "between 1 and 3 objects, no conversion", skip the format string:
PyObject *obj, *callback = NULL;
if (!PyArg_UnpackTuple(args, "ref", 1, 2, &obj, &callback)) return NULL;
/* both are borrowed references */
Argument Clinic
Writing PyArg_ParseTupleAndKeywords by hand is repetitive, is slower than it needs to be, and produces signatures that help() and inspect.signature cannot read. CPython's own solution is Argument Clinic, a preprocessor: you write a declarative signature in a comment block, run a tool, and it generates a fast keyword parser, the docstring, and the __text_signature__ that makes introspection work.
/*[clinic input]
mymod.resize
width: int
height: int
mode: str = "nearest"
Resize the image.
[clinic start generated code]*/
It is a CPython-internal tool rather than a public one, but it ships with the source and is usable in third-party projects. You do not need it, and this course does not assume it — but if you find yourself hand-writing many keyword parsers, know it exists. Its most valuable output is the fast keyword parsing that PyArg_ParseTupleAndKeywords cannot provide for METH_FASTCALL functions.
Building return values
Py_BuildValue is the inverse of PyArg_ParseTuple and returns a new strong reference:
return Py_BuildValue("i", n); /* an int */
return Py_BuildValue("(iid)", x, y, dist); /* a 3-tuple */
return Py_BuildValue("{s:i,s:s}", "n", n, "name", name); /* a dict */
return Py_BuildValue("y#", buffer, (Py_ssize_t)len); /* bytes */
Two units matter for ownership: O takes an object and increfs it, while N takes an object and steals your reference. Use N for something you just created and would otherwise have to decref:
/* Without N you would need a temporary and a Py_DECREF on both paths. */
return Py_BuildValue("(NN)", PyLong_FromLong(a), PyLong_FromLong(b));
For simple cases the direct constructors are clearer and faster: PyLong_FromLong, PyFloat_FromDouble, PyUnicode_FromString, PyUnicode_FromFormat, PyBytes_FromStringAndSize, PyTuple_Pack.
Calling back into Python
Wrapping a library almost always means invoking a Python callable from C. The modern, allocation-light APIs:
PyObject_CallNoArgs(fn) | Zero arguments. No tuple built. |
PyObject_CallOneArg(fn, arg) | One argument. No tuple built. |
PyObject_Vectorcall(fn, args, nargsf, kwnames) | The general fast path: a C array of arguments. |
PyObject_CallFunctionObjArgs(fn, a, b, NULL) | Variadic, NULL-terminated, objects only. |
PyObject_Call(fn, args_tuple, kwargs_dict) | The fully general form when you already have a tuple and dict. |
PyObject_CallMethodNoArgs(obj, name) / PyObject_CallMethodOneArg | Method calls without building a bound method object. |
PyObject_CallFunction(fn, "is", n, s) | Convenience using Py_BuildValue format. Convenient, not fast. |
All of them return a new strong reference, or NULL with an exception set. Three obligations at every call site:
- Check for NULL. A Python callback can raise anything.
- Decref the result, even when you ignore it.
- Assume the world changed. The callback ran arbitrary code: it may have mutated your containers, released and reacquired the interpreter, imported modules, or triggered a garbage collection. Re-validate anything you cached across the call, and never hold a borrowed reference across one.
PyObject *res = PyObject_CallOneArg(state->callback, item);
if (res == NULL) {
return -1; /* propagate; exception already set */
}
Py_DECREF(res);
Measure the convention difference without writing C, using functions that already use each one:
import timeit
# METH_O (one arg, no parsing) vs METH_VARARGS-style parsing:
print(timeit.timeit("len(x)", setup="x=[1,2,3]", number=2_000_000))
print(timeit.timeit("divmod(7, 3)", number=2_000_000))
# Introspectability is a real consequence of the parsing choice:
import math, inspect
print(inspect.signature(math.hypot)) # Argument Clinic gave this a signature
help(math.pow) # note the rendered signature line
Then sketch, in C, a function chunk(data, size, *, pad=False) and decide: which convention, which format string, which units, and exactly where you must call PyBuffer_Release.
Further reading
- C API — Implementing Functions and Methods.
PyMethodDefand everyMETH_flag. - C API — Parsing Arguments and Building Values. The complete format-unit table.
- C API — Call Protocol. Vectorcall, and the full set of calling APIs.
- PEP 590 — Vectorcall. The design and the reasoning about allocation cost.
- Argument Clinic How-To.