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:
Compile it as a shared library. On macOS:
On Linux, the extension is typically .so.
Then call it from Python:
Output:
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.
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.
After building the extension, Python code can import it like a normal module:
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
ctypesfor simple existing C functions - use
cffifor more structured C FFI work - use
pybind11for 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.
- '
ctypesis 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.

