• Martin Thoma
  • Home
  • Categories
  • Tags
  • Archives
  • Support me

Python ctypes

Contents

  • Python ctypes
    • Motivation
    • Example
    • What happens
    • Caveats
    • See also

One pseudo-problem people often mention when talking about Python is that Python is (too) slow. What they seem to forget or don't know is that you can call C code from Python with ctypes. So you can get almost as fast as you can get with C; you're not limited by the language in that respect. And most of the time your code has other issues when it is too slow.

Now, you can also wrap Rust code with ctypes for Python ☺

Motivation

As a very simple example, I prepared a dumb Fibonacci implementation written in Python:

def fib(n):
    """Calculate the n-th Fibonacci number."""
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)


if __name__ == "__main__":
    n = 37
    print("The %ith Fibonacci number is %i." % (n, fib(n)))

and exactly the same for Rust

pub extern fn fib(n: u32) -> u32 {
    if n <= 1 {
        return n;
    } else {
        return fib(n-1) + fib(n-2);
    }
}

fn main() {
    let n = 37;
    println!("The {0}th Fibonacci number is {1}.", n, fib(n))
}

The execution times are quite different. Rust needs 0.40 seconds while Python 3 needs 15.7 seconds. That is almost 40× the time of Rust!

Wouldn't it be great if we could call the Rust function from Python?

Example

I'll explain in the next chapters what is done, but at first you should see that there are only minor changes / overhead:

fibonacci.rt:

#![crate_type = "dylib"]

#[no_mangle]
pub extern fn fib(n: u32) -> u32 {
    if n <= 1 {
        return n;
    } else {
        return fib(n-1) + fib(n-2);
    }
}

Call rustc -O fibonacci.rt to generate the library.

Python:

#!/usr/bin/env python

import ctypes

fiblib = ctypes.CDLL("./libfibonacci.so")
fib = fiblib.fib
n = 37
print("The %ith Fibonacci number is %i." % (n, fib(n)))

Now, taking the Python code, it takes only 0.44 seconds!

What happens

The line #![crate_type = "dylib"] tells rustc that it has to create a dynamic library.

The line #[no_mangle] tells the compiler not to mangle the name fib. This is important so that we can later use it from Python. (I think names are mangled to prevent name clashes ... so it's a kind of name-spacing.)

Then we load the C DLL with ctypes.CDLL("./libfibonacci.so") and use it as expected. Pretty easy, isn't it?

Caveats

Python makes some things very simple which are not that simple at all. Think about numbers, for example. In Rust, you have integers with 64 bits. But in Python you have arbitrary length integers. This might lead to problems.

See also

  • Calling Rust from C (and Python!)
  • doc.rust-lang.org/book/ffi: Foreign Function Interface
  • doc.rust-lang.org: FFI attributes
  • rustbyexample.com: Crates

Published

Apr 2, 2015
by Martin Thoma

Category

Code

Tags

  • Python 141
  • Rust 3

Contact

  • Martin Thoma - A blog about Code, the Web and Cyberculture
  • E-mail subscription
  • RSS-Feed
  • Privacy/Datenschutzerklärung
  • Impressum
  • Powered by Pelican. Theme: Elegant by Talha Mansoor