Read about our upcoming Code of Conduct on this issue

Commit d05e40d3 authored by Pierre Augier's avatar Pierre Augier
Browse files

First ~ working implementation

parent e84ef70b49f6
......@@ -13,4 +13,6 @@ dist*
.tox/*
.coverage/*
*.egg-info/*
\ No newline at end of file
*.egg-info/*
**/_pythran/_pythran_*
\ No newline at end of file
FluidPythran: Pythran annotations in Python files
=================================================
.. warning ::
This is really just a prototype.
This pure-Python package will provide few supplementary pythran commands,
namely ``pythran block`` and ``pythran def``.
namely ``pythran block`` and ``pythran def`` (see examples in the doc folder).
The code of the numerical kernels can stay in the modules and in the classes
were they where written. The Pythran files (i.e. the files compiled by
Pythran), which are usually written by the user, are produced automatically.
The Python files have to be "compiled" with the command line tool
`fluidpythran`.
The code continues to work fine without Pythran, which is used only when
available.
from fluidpythran import create_pythran_file
path = "example_class.py"
create_pythran_file(path)
\ No newline at end of file
import numpy as np
import fluidpythran as fp
# pythran import numpy as np
from fluidpythran import FluidPythran
fp = FluidPythran()
class MyClass:
......@@ -13,23 +19,39 @@ class MyClass:
a = self.a
b = self.b
if fp.is_pythranized():
if fp.is_pythranized:
result = fp.use_pythranized_block("block0")
else:
# pythran block (
# float[][] a, b;
# int n
# ) -> result
# silly comment
# another comment here
# pythran block (
# float[][][] a, b;
# int n
# ) -> result
result = 0.0
result = np.zeros_like(a)
for _ in range(n):
result += a**2 + b**3
result += a ** 2 + b ** 3
return result
if __name__ == "__main__":
shape = 100, 100
a = np.random.rand(*shape)
b = np.random.rand(*shape)
obj = MyClass(a, b)
obj.compute(10)
if fp.is_pythranized:
ret = obj.compute(10)
fp.is_pythranized = False
ret1 = obj.compute(10)
fp.is_pythranized = True
assert np.allclose(ret, ret1)
print("allclose OK")
import numpy as np
import fluidpythran as fp
from fluiddyn.util import mpi
from fluidpythran import FluidPythran
fp = FluidPythran()
# pythran import numpy as np
# pythran def func(float[][], float[][])
# pythran def func(int[][], float[][])
......
from tokenize import (
tokenize,
untokenize,
COMMENT,
INDENT,
DEDENT,
OP,
STRING,
NAME,
)
from token import tok_name
from io import BytesIO
path = "example_class.py"
blocks = []
with open(path) as file:
code = file.read()
has_to_find_name_block = False
g = tokenize(BytesIO(code.encode("utf-8")).readline)
for toknum, tokval, a, b, c in g:
if toknum == NAME and tokval == "use_pythranized_block":
has_to_find_name_block = True
if has_to_find_name_block and toknum == STRING:
name_block = eval(tokval)
print("Block", name_block)
has_to_find_name_block = False
blocks.append(name_block)
print((tok_name[toknum], tokval))
print(blocks)
import inspect
import importlib
from ._version import __version__
from .toolchain import create_pythran_code, create_pythran_file
__all__ = [
"__version__",
"monkey_patch_function",
"is_pythranized",
"use_pythranized_block",
"create_pythran_code",
"create_pythran_file",
"FluidPythran",
]
def monkey_patch_function(func):
return func
class FluidPythran:
def __init__(self, use_pythran=True):
if not use_pythran:
self.is_pythranized = False
return
frame = inspect.stack()[1]
module_name = inspect.getmodulename(frame.filename)
module_pythran_name = "_pythran._pythran_" + module_name
try:
self.module_pythran = importlib.import_module(module_pythran_name)
self.is_pythranized = True
except ModuleNotFoundError:
self.is_pythranized = False
else:
self.arguments_blocks = getattr(
self.module_pythran, "arguments_blocks"
)
def monkey_patch_function(self, func):
if self.is_pythranized:
return getattr(self.module_pythran, func.__name__)
else:
return func
def is_pythranized():
return False
def use_pythranized_block(self, name):
if not self.is_pythranized:
raise ValueError(
"`use_pythranized_block` has to be used protected "
"by `if fp.is_pythranized`"
)
def use_pythranized_block(name):
func = getattr(self.module_pythran, name)
argument_names = self.arguments_blocks[name]
frame = inspect.currentframe()
try:
locals_caller = frame.f_back.f_locals
finally:
del frame
frame = inspect.currentframe()
try:
locals_caller = frame.f_back.f_locals
finally:
del frame
raise NotImplementedError
arguments = [locals_caller[name] for name in argument_names]
return func(*arguments)
from tokenize import tokenize, untokenize, COMMENT, INDENT, DEDENT, STRING, NAME
from token import tok_name
from io import BytesIO
import inspect
from runpy import run_path
from pathlib import Path
import black
def parse_py(path):
blocks = []
signatures_blocks = {}
code_blocks = {}
functions = set()
signatures_func = {}
imports = []
with open(path) as file:
code = file.read()
has_to_find_name_block = False
has_to_find_signatures = False
has_to_find_code_block = False
in_def = False
g = tokenize(BytesIO(code.encode("utf-8")).readline)
for toknum, tokval, a, b, c in g:
if (
toknum == COMMENT
and tokval.startswith("# pythran ")
and "import" in tokval
):
imports.append(tokval.split("# pythran ", 1)[1])
if toknum == NAME and tokval == "use_pythranized_block":
has_to_find_name_block = True
has_to_find_signatures = True
has_to_find_code_block = "after use_pythranized_block"
if has_to_find_name_block and toknum == STRING:
name_block = eval(tokval)
has_to_find_name_block = False
blocks.append(name_block)
if toknum == COMMENT and tokval.startswith("# pythran def "):
in_def = True
signature_func = tokval.split("# pythran def ", 1)[1]
name_func = signature_func.split("(")[0]
functions.add(name_func)
if name_func not in signatures_func:
signatures_func[name_func] = []
if ")" in tokval:
in_def = False
signatures_func[name_func].append(signature_func)
if in_def:
if toknum == COMMENT:
signature_func += tokval
if ")" in tokval:
in_def = False
signatures_func[name_func].append(signature_func)
if has_to_find_signatures and toknum == COMMENT:
if tokval.startswith("# pythran block "):
in_signature = True
signature = tokval.split("# pythran block ", 1)[1]
elif in_signature:
signature += tokval[1:].strip()
if ")" in tokval and "-> (" not in tokval:
in_signature = False
if name_block not in signatures_blocks:
signatures_blocks[name_block] = []
signatures_blocks[name_block].append(signature)
if has_to_find_code_block == "in block":
code_blocks[name_block].append((toknum, tokval))
if toknum == DEDENT:
code_blocks[name_block] = untokenize(code_blocks[name_block])
has_to_find_code_block = False
if (
has_to_find_code_block == "after use_pythranized_block"
and toknum == INDENT
):
has_to_find_code_block = "in block"
code_blocks[name_block] = []
# print((tok_name[toknum], tokval))
return (
blocks,
signatures_blocks,
code_blocks,
functions,
signatures_func,
imports,
)
def create_pythran_code(path_py):
blocks, signatures_blocks, code_blocks, functions, signatures_func, imports = parse_py(
path_py
)
print(signatures_blocks)
code_pythran = "\n" + "\n".join(imports) + "\n"
mod = run_path(path_py)
for name_func in functions:
signatures = signatures_func[name_func]
for signature in signatures:
code_pythran += f"# pythran export {signature}\n"
func = mod[name_func]
code = inspect.getsource(func)
code_pythran += f"\n{code}\n\n"
variables_types_block = {}
return_block = {}
for name_block, types_variables in signatures_blocks.items():
variables_types_block[name_block] = []
for types_variables1 in types_variables:
variables_types = {}
lines = types_variables1.split("(", 1)[1].split(")")[0].split(";")
for line in lines:
type_, *variables = line.split()
for variable in variables:
variables_types[variable.replace(",", "")] = type_
variables_types_block[name_block].append(variables_types)
if types_variables and "->" in types_variables1:
return_block[name_block] = types_variables1.split("->", 1)[1]
# add "pythran export" for blocks
for name_block, variables_types0 in variables_types_block.items():
for variables_types in variables_types0:
str_variables = ", ".join(variables_types.values())
code_pythran += f"# pythran export {name_block}({str_variables})\n"
arguments_blocks = {}
# add code for blocks
for name_block, variables_types0 in variables_types_block.items():
variables = variables_types0[0].keys()
arguments_blocks[name_block] = list(variables)
str_variables = ", ".join(variables)
code_pythran += f"def {name_block}({str_variables}):\n"
code_block = code_blocks[name_block]
code_pythran += 4 * " " + code_block.replace("\n", "\n" + 4 * " ") + "\n"
if name_block in return_block:
code_pythran += f" return {return_block[name_block]}\n"
code_pythran += "# pythran export arguments_blocks\n"
code_pythran += "arguments_blocks = " + str(arguments_blocks)
code_pythran = black.format_str(code_pythran, line_length=82)
return code_pythran
def create_pythran_file(path_py):
code_pythran = create_pythran_code(path_py)
if not code_pythran:
return
print("code_pythran\n", code_pythran)
path = Path(path_py)
path_dir = path.parent / "_pythran"
path_dir.mkdir(exist_ok=True)
path_pythran = path_dir / ("_pythran_" + path.name)
with open(path_pythran, "w") as file:
file.write(code_pythran)
print(f"File {str(path_pythran)} written")
......@@ -6,4 +6,7 @@ keywords = pythran
license = CeCILL-B License
classifiers =
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
\ No newline at end of file
Programming Language :: Python :: 3.6
[options]
install_requires = black
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment