Python
C++
C Programming
Interoperability
Foreign Function Interface

Calling C/C from Python?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Calling native C or C++ code from Python is a standard way to reuse existing libraries or move performance-critical work out of pure Python. The best interface depends on what you already have: ctypes is convenient for plain C libraries, while tools such as pybind11 are often a better fit for modern C++.

Using ctypes With a C Shared Library

ctypes is built into Python and works well when you already have a compiled C API with simple function signatures.

First, write a small C library:

c
1// add.c
2int add(int a, int b) {
3    return a + b;
4}

Compile it as a shared library. On macOS:

bash
cc -shared -o libadd.dylib add.c

On Linux, the extension is typically .so.

Then call it from Python:

python
1import ctypes
2
3lib = ctypes.CDLL("./libadd.dylib")
4lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
5lib.add.restype = ctypes.c_int
6
7result = lib.add(2, 3)
8print(result)

Output:

text
5

The argtypes and restype declarations are important because they tell ctypes how to marshal Python values into the native call and how to interpret the return value.

Why C++ Needs More Care

C++ is harder to call directly because of name mangling, overloaded functions, classes, exceptions, and object lifetimes. A common approach is to expose a small C-compatible wrapper around the C++ code, then call that wrapper from Python.

cpp
1// math.cpp
2class Adder {
3public:
4    int add(int a, int b) const {
5        return a + b;
6    }
7};
8
9extern "C" int add_numbers(int a, int b) {
10    Adder adder;
11    return adder.add(a, b);
12}

The extern "C" declaration gives the exported function a C-style symbol name, which makes it much easier for Python FFI tools to load.

A Better C++ Experience With pybind11

If you want Python to work naturally with C++ classes and methods, pybind11 is usually more ergonomic than writing manual wrappers for everything.

cpp
1#include <pybind11/pybind11.h>
2
3int add(int a, int b) {
4    return a + b;
5}
6
7PYBIND11_MODULE(example, m) {
8    m.def("add", &add, "Add two integers");
9}

After building the extension, Python code can import it like a normal module:

python
import example

print(example.add(10, 32))

This route is excellent when the native codebase is already in C++ and you want a clean Python-facing API rather than a thin low-level binding.

Other Options

cffi is another good choice, especially when you want a more explicit foreign-function interface than ctypes and you are targeting C libraries.

For CPython-specific, maximum-control bindings, you can also write a native extension module against the Python C API directly. That gives you the most power, but it also has the highest maintenance cost.

So the rough decision guide is:

  • use ctypes for simple existing C functions
  • use cffi for more structured C FFI work
  • use pybind11 for modern C++ bindings
  • use the Python C API directly only when you need very fine control

Common Pitfalls

The most common issue is forgetting that Python and native code must agree exactly on types. A wrong pointer type or return type can produce crashes instead of friendly exceptions.

Another pitfall is memory ownership. If C allocates memory and Python reads it, your interface must define clearly who frees that memory and when.

C++ exceptions are another danger. Throwing a C++ exception through a plain C boundary is undefined behavior, so wrappers should catch native exceptions and translate them into Python errors in a controlled way.

Finally, build and platform details matter. Shared-library extensions, compiler flags, symbol visibility, and architecture mismatches can all break an otherwise correct binding.

Summary

  • Python can call native code through several FFI techniques.
  • 'ctypes is the simplest built-in option for plain C libraries.'
  • C++ often works best through a wrapper layer or a binding tool like pybind11.
  • Type declarations and memory ownership rules must be explicit.
  • Choose the binding approach based on whether you have a small C API, a larger C library, or a modern C++ codebase.

Course illustration
Course illustration

All Rights Reserved.