Python
C++
C Programming
Interoperability
Foreign Function Interface

Calling C/C from Python?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.