ctypes & CFFI¶
Overview¶
ctypes and CFFI enable calling C libraries:
- ctypes: Built-in, low-level C interface
- CFFI: Higher-level, more Pythonic
- Performance: Call optimized C code directly
- Integration: Use existing C libraries in Python
ctypes Basics¶
Loading C Libraries¶
import ctypes
import platform
# Load system library
if platform.system() == "Windows":
libc = ctypes.CDLL("msvcrt")
elif platform.system() == "Darwin": # macOS
libc = ctypes.CDLL("libc.dylib")
else: # Linux
libc = ctypes.CDLL("libc.so.6")
# Call C function
result = libc.abs(-42)
print(result) # 42
Argument and Return Types¶
import ctypes
import math
# Load math library
libm = ctypes.CDLL("libm.so.6")
# Specify argument and return types
libm.sqrt.argtypes = [ctypes.c_double]
libm.sqrt.restype = ctypes.c_double
# Call with proper types
result = libm.sqrt(16.0)
print(result) # 4.0
Working with C Structs¶
import ctypes
# Define C struct
class Point(ctypes.Structure):
_fields_ = [("x", ctypes.c_double),
("y", ctypes.c_double)]
# Create instance
p = Point(x=3.0, y=4.0)
print(p.x, p.y) # 3.0 4.0
# Nested struct
class Rect(ctypes.Structure):
_fields_ = [("top_left", Point),
("bottom_right", Point)]
r = Rect()
r.top_left.x = 0.0
r.top_left.y = 10.0
Arrays and Pointers¶
import ctypes
import numpy as np
# Create array
arr = (ctypes.c_int * 5)(1, 2, 3, 4, 5)
print(arr[0]) # 1
# Pointer to array
ptr = ctypes.pointer(arr)
# NumPy integration
np_arr = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
ptr = np_arr.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
CFFI: More Pythonic Interface¶
Basic CFFI Usage¶
from cffi import FFI
ffi = FFI()
# Define C interface
ffi.cdef("""
double sqrt(double x);
int abs(int x);
""")
# Load library
C = ffi.dlopen("libm.so.6")
# Use C functions
print(C.sqrt(16.0)) # 4.0
print(C.abs(-42)) # 42
CFFI Structs and Functions¶
from cffi import FFI
ffi = FFI()
# Define struct and function
ffi.cdef("""
struct Point {
double x;
double y;
};
double distance(struct Point* p1, struct Point* p2);
""")
# Implement in Python (or use existing C library)
@ffi.callback("double(struct Point*, struct Point*)")
def distance_callback(p1, p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
return (dx**2 + dy**2)**0.5
# Create structs
p1 = ffi.new("struct Point*")
p1.x = 0.0
p1.y = 0.0
p2 = ffi.new("struct Point*")
p2.x = 3.0
p2.y = 4.0
# Compute distance
dist = distance_callback(p1, p2)
print(dist) # 5.0
NumPy Integration¶
Calling C with NumPy Arrays¶
import numpy as np
import ctypes
def process_array(arr):
"""Process NumPy array in C."""
arr = np.asarray(arr, dtype=np.float64)
# Get pointer to data
data_ptr = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
# Call C function with pointer
# libm_c.process_float_array(data_ptr, len(arr))
return arr
# Use it
data = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
result = process_array(data)
Real-World ML Example¶
Calling BLAS from ctypes¶
import numpy as np
import ctypes
# Load BLAS library
libblas = ctypes.CDLL("libblas.so.3")
# Define DGEMM (matrix multiply)
libblas.dgemm_.argtypes = [
ctypes.c_char_p, # TRANSA
ctypes.c_char_p, # TRANSB
ctypes.POINTER(ctypes.c_int), # M
ctypes.POINTER(ctypes.c_int), # N
ctypes.POINTER(ctypes.c_int), # K
ctypes.POINTER(ctypes.c_double), # ALPHA
ctypes.c_void_p, # A
ctypes.POINTER(ctypes.c_int), # LDA
ctypes.c_void_p, # B
ctypes.POINTER(ctypes.c_int), # LDB
ctypes.POINTER(ctypes.c_double), # BETA
ctypes.c_void_p, # C
ctypes.POINTER(ctypes.c_int), # LDC
]
def matrix_multiply_blas(A, B):
"""Multiply matrices using BLAS."""
A = np.asarray(A, dtype=np.float64, order='F')
B = np.asarray(B, dtype=np.float64, order='F')
m, k = A.shape
k, n = B.shape
C = np.zeros((m, n), dtype=np.float64, order='F')
one = ctypes.c_double(1.0)
zero = ctypes.c_double(0.0)
m_c = ctypes.c_int(m)
n_c = ctypes.c_int(n)
k_c = ctypes.c_int(k)
libblas.dgemm_(
b'N', b'N',
ctypes.byref(m_c),
ctypes.byref(n_c),
ctypes.byref(k_c),
ctypes.byref(one),
A.ctypes.data_as(ctypes.c_void_p),
ctypes.byref(m_c),
B.ctypes.data_as(ctypes.c_void_p),
ctypes.byref(k_c),
ctypes.byref(zero),
C.ctypes.data_as(ctypes.c_void_p),
ctypes.byref(m_c),
)
return C
Summary: ctypes vs CFFI¶
| Aspect | ctypes | CFFI |
|---|---|---|
| Pythonicity | Low | High |
| Type Safety | Manual | Automatic |
| Complexity | Simple | Medium |
| Performance | Good | Excellent |
| NumPy Integration | Good | Good |
-
Related Topics¶
- [03 Jit Compilation & Optimization](/05-py3/09-bytecode-and-execution/(03-jit-compilation-optimization/) - Alternative to C extensions
- 00 Readme - Performance considerations
- 05 Custom Operators - Custom CUDA kernels