in python. One possible way to do it is to create a shared object(.so) file by using f2py.
Simple example of using f2py can be found easily.
But, in case of complicate fortran program which use 'make', or in case of the subroutine we want to use is a part of larger library, it is not clear how to
achieve it.
The steps are
(1) Prepare a module code which includes all the subroutines which wants to be
ported to python.
It is recommended the subroutines includes both input and outputs explicitly
by 'intent(in)' and 'intent(out)' properties.
(2) create a static library for the subroutine (first compile for object files)
ar crsv lib[name].a [object files]
(3) use f2py to create signature file (.pyf) .
f2py [source file] -m [package name] -h [signature file name]
Here the [source file] contains the subroutines or its wrapper to be imported to python. signature file (.pyf) contains a module for python which contains subroutines.
Importing sub module does not work well. Thus, always prepare [source file]
as a wrapper with subroutines not modules.
(4) edit the signature fil( .pyf) as necessary. (Only leave the subroutines to be imported)
(5) created shared object library(.so) file using f2py
(If path of library is the same, "-L." would work?)
f2py -c [signature file .pyf] [source file] -L[absolute path for library]-l[library name] -llapack
(6) In the python,
import [package name]
and now one can use subroutines,
[packagename].[subroutine]
(7) in case that the library(.so) file is located in different folder,
add the library path before import
import sys
sys.path.insert(0,'[Library Path]')
# Another way to use f2py is
(6) In the python,
import [package name]
and now one can use subroutines,
[packagename].[subroutine]
(7) in case that the library(.so) file is located in different folder,
add the library path before import
import sys
sys.path.insert(0,'[Library Path]')
- The simple case: If there is only one fortran file, one can do
- (1) edit/comment the fortran file "my_lib.f90" with "!f2py " comments or explicit "intent" expressions.
- (2) Use following command to create " my_lib.so " file which can be imported in python by "import my_lib" ,
f2py -c -m my_lib my_lib.f90
# Another way to use f2py is
directly add "cf2py intent([in/out]) [variable]" in the fortran source code
and compile
"python -m numpy.f2py -c -m [module name] [fortran source]"
# In Windows, there seems to be some issue with the version of mingw-w64.
It seems I have to use "x86_64" version of mingw-w64 instead of "i686" version.
More details on the installation.
(https://python-at-risoe.pages.windenergy.dtu.dk/compiling-on-windows/configuration.html)
# In Windows, one can use "ar" to create library as like LINUX with mingw-w64.
# Currently(2025.02.25), there seems to be a problem using f2py and "meson" build system in Windows... I am not sure how to fix the error. The same code can be compiled in linux. --> this can be solved by install "meson" and "ninja" package with "pip"
#----custumization of docstring of f2py:
One may want to use custum docstring for f2py generated functions.
To do this, one needs to overwrite the docstrings.
(1) Suppose original fortran code have docstring between each "subroutine" and "implicit none" and python package is created by f2py.
(2) Use the following script after loading the package.
#-----replace f2py generated docsting with custom docstring from fortran file.
import re
import special_py as sp # Replace with your actual f2py module name
def extract_fortran_docstrings_by_block(fortran_file_path):
"""
Parses a Fortran source file to extract documentation blocks
between 'subroutine ...' and 'implicit none'.
"""
docstrings = {}
with open(fortran_file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Regex pattern to capture the text between 'subroutine' (plus the name)
# and 'implicit none'. Uses re.DOTALL (re.S) for multiline matching.
# Note: This assumes 'implicit none' appears reliably after the docs you want.
pattern = re.compile(
r"subroutine\s+(\w+)\s*\(.*?\).*?(?P<doc_block>.*?)\s*implicit none",
re.IGNORECASE | re.DOTALL
)
for match in pattern.finditer(content):
func_name = match.group(1).lower()
doc_block = match.group('doc_block')
# Clean up the doc block: remove the '!' prefix and format for Python
clean_doc_lines = []
for line in doc_block.strip().splitlines():
stripped_line = line.strip()
if stripped_line.startswith('!'):
# Append everything after the '!' and a single space
clean_doc_lines.append(stripped_line[1:].strip())
else:
# If a line doesn't start with '!' (e.g., blank lines), include it as a blank line
clean_doc_lines.append("")
docstrings[func_name] = "\n".join(clean_doc_lines).strip()
return docstrings
def set_custom_docstrings(module, docstring_map):
"""
Overwrites the docstring for specified functions within a module.
"""
for func_name, custom_doc in docstring_map.items():
func_obj = getattr(module, func_name, None)
if func_obj is not None:
func_obj.__doc__ = custom_doc
# Optional: print(f"Docstring for '{func_name}' updated.")
# --- Main Execution ---
# 1. Define the path to your original Fortran source file
FORTRAN_SOURCE = 'special_functions.f90' # Replace with the correct file path
# 2. Extract the docstrings from the Fortran file
try:
fortran_docs = extract_fortran_docstrings_by_block(FORTRAN_SOURCE)
# 3. Apply the extracted docstrings to the imported Python module functions
set_custom_docstrings(sp, fortran_docs)
print(f"Successfully updated docstrings for {len(fortran_docs)} functions.")
# Verification (Example for airya)
print("\n--- Updated Docstring for sp.airya ---")
print(sp.airya.__doc__)
except FileNotFoundError:
print(f"Error: Could not find the Fortran source file at '{FORTRAN_SOURCE}'")
except Exception as e:
# Handle potential encoding errors or regex errors
print(f"An error occurred during processing: {e}")
댓글 없음:
댓글 쓰기