nativegate
← Blog

F2PY: when it works and where it stops

F2PY — NumPy's Fortran-to-Python wrapper — is one of the oldest, best-maintained tools in the scientific Python stack. This post is a plain evaluation: the situations where reaching for it is genuinely the right call, and an honest list of the things it does not set out to solve. (For the direct scope comparison with nativegate, see F2PY vs nativegate; nativegate delegates Fortran binding to f2py.)

When f2py is the right call

  • Well-specified source. Free-form Fortran 90+ with explicit INTENT declarations, module structure, and no exotic state. This is the f2py sweet spot and it remains great there.
  • One library, one integration. You need .so-level access from Python and you already have a build system, tests, and deployment story. f2py slots straight in.
  • Argument-level control. f2py's signature directives (!f2py intent(...)) let you hand-tune argument behavior case by case; nothing generated beats explicitness when a routine is weird.
  • Longevity. Decades of use, NumPy-core maintenance, and published guidance. Tools that outlive their authors are worth taking seriously.

Where it stops

f2py stops, by design, at the Python-Fortran boundary. What it is not is a modernization workflow; each of these is left to you:

  • Intent inference. Old code without INTENT declarations needs a human or a tool to decide argument directions. f2py guesses on intent when not told, and a wrong guess becomes a silent wrong answer rather than a build error.
  • Legacy-deck reality. INCLUDE-deck composition, unresolved parameterized kinds, and 1980s fixed-form dialects may pass the binding and then fail at the edges. Building the expanded-parse shims is a tool-week of its own.
  • Packaging and distribution. A compiled extension is not an installable package. CMake/scikit-build orchestration, locked wheels, and CI across compilers are yours to write and re-write.
  • Testing and numerical regression. No binding generator ships with a baseline: recording known configurations and failing on drift is a separate harness to build.
  • Concurrency and state. A f2py module will happily let two threads collide inside COMMON-block state. Nothing stops you; nothing warns you.
  • Service and deployment. HTTP, containers, Kubernetes, auth: all downstream work, ungenerated.

The pattern, generally

f2py is excellent at what it covers and neutral about what's around it. If you own the surrounding work all the way to production, that "neutral" is fine. If not, the honest framing is that f2py resets your problem from "Fortran and Python" to "Python package engineering, testing, deployment, and validation."

Which is exactly the gap nativegate is built to close — without replacing f2py, and in the same conviction f2py embodies: the binding is the smallest step of the modernization.

The direct comparison → Next: wrapping legacy Fortran with f2py