f2py: binding Fortran and Python

I  have recently started using f2py to call Fortran from Python.  I have found this useful for two reasons: speeding up Python scripts by calling compiled Fortran code, and using Python as a unit testing framework for Fortran modules.   Unfortunately, the documentation for f2py is rather sparse, and may not be completely up to date.   In this note, I will hopefully prevent you from wasting a lot of time figuring out how to pass array arguments, and return array results.

Array arguments

Passing array arguments is a critical when working with numerical algorithms.  F2py handles this well, but it is difficult to figure it out how to do it correctly.  Here is a  simple example with some Fortran 90 routines:

module test
contains
subroutine foo (a)
    implicit none
    integer, intent(in) :: a
    print*, "Hello from Fortran!"
    print*, "a=",a
end subroutine foo
function bar (len_a, a)
    implicit none
    integer, intent(in) :: len_a
    real, dimension(len_a), intent(in) :: a
    real, dimension(len_a) :: bar
    !f2py depend(len_a) a, bar
    integer :: i
    real, dimension(len_a) :: b
    do i=1,len_a
        b(i) = 2.0*a(i)
    end do
    bar = b
end function bar
subroutine sub (a, len_a, a_out)
    implicit none
    real, dimension(len_a), intent(in) :: a
    integer, intent(in) :: len_a
    real, dimension(len_a), intent(out) :: a_out
    integer :: i
    do i=1,len_a
        a_out(i) = 2.0*a(i)
    end do
end subroutine sub
end module test

Function “foo” is quite straightforward. Function “bar” is a little more complex, because it accepts an array argument and returns an array. If you’re new to Fortran, it will seem strange to pass the length of the array along with the array, but you need that information to declare the output array. f2py needs to know that the input array a and the output array bar both depend on the argument len_a.  The special comment line

!f2py depend(len_a) a, bar

is mandatory!  It tells f2py that a depends on len_a.  If you omit this comment or the corresponding one for, the function will not work correctly.  You will get strange errors like

ValueError: failed to create intent(cache|hide)|optional array-- must have defined
dimensions but got (0,)

Fortran subroutines

Now look at the subroutine called sub.  On the Fortran side, it has three arguments: two inputs and an output.  However, Python only has functions,  and all non-array arguments are passed by value.  How do you reconcile this?  When the Fortran subroutine is called from Python, the intent(out) variables are returned as a function result, or a tuple of results if there are more than one.  Compare the Fortran code above with the Python call below to see what I mean.

Building

This code can be compiled using the “fast and smart” method described in the docs:

f2py -c -m hello hello.f90

Here is the Python code that calls the Fortran routines:

#!/usr/bin/env python
import hello
from numpy import *
a = arange(0.0, 10.0, 1.0)
len_a = len(a)
print "foo:"
hello.test.foo(len_a)
print "bar:"
a_out = hello.test.bar(len_a, a)
print a_out
print "sub:"
a_out = hello.test.sub(a, len_a)
print a_out

You might also want to check out this rather complicated f2py example.

8 thoughts on “f2py: binding Fortran and Python”

  1. There is a typo in your f2py command. It should be `f2py -c -m hello hello.f90`. The argument that follows -m is the module name.

  2. Pingback: Error compiling basic Fortran program using f2py

  3. My previous comment isn’t correct, since the command as given *does* work. However, putting the module name immediately after -m seems a bit clearer to me, and that is what is suggested in the help given by `f2py -h`.
    Anyway, thanks for your ongoing blog! There’s a lot of very useful stuff here.

  4. It is useful to blend python and fortran. However, these days I encountered a problem with blending these two languages. The problem is: 1, I compile and execute a fortran code with three simple loops(code will be pasted below), that the program costs just 15-second; 2, in order to be used by python, I change the fortran code to subroutine, compile it by f2py, and execute it with python, but this subroutine cost nearly 50-second. Could you please give me some suggestion? Thank you very much!
    grid_nst=0
    do i=1,1401
    do j=1,1201
    glat=(j-1)*0.1
    glon=(i-1)*0.1
    do ll=1,5000
    distance=sqrt((glat-lat(ll))**2+(glon-lon(ll))**2)
    if ( distance<= 2.5 ) then
    grid_nst(j,i)=grid_nst(j,i)+1
    endif
    enddo
    enddo
    enddo

    Fortran code for python:subroutine test(nst,lat,lon), the others are same
    Python:var=loop.test(lat,lon)
    f2py compile: f2py -c tt.f90 -m loop –f90exec=gfortran

    1. SJ, what is the run time if you call the Fortran subroutine from a Fortran program (without Python involved)? Does the Python/Fortran version take 50 seconds every time it runs, or only the first time?

  5. Thank you for replying, CRAIG. The run time is nearly 50 seconds when I call Fortran subroutine compiled by f2py through Python , but is only 15 seconds when call the same subroutine through Fortran program(without Python involved). Unfortunately, the Python version takes 50s every time. I check the performance of the Python version, the result is below:
    /———————–\

    \———————–/
    Overall time spent in …
    (a) wrapped (Fortran/C) functions : 49705 msec
    (b) f2py interface, 1 calls : 113 msec
    (c) call-back (Python) functions : 0 msec
    (d) f2py call-back interface, 0 calls : 0 msec
    (e) wrapped (Fortran/C) functions (actual) : 49705 msec

    Use -DF2PY_REPORT_ATEXIT_DISABLE to disable this message.
    Exit status: 0

    Thanks again~

Leave a Reply to Tom Cancel Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.