Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add: Make function_name parametrized #166

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ public class Model {

`m2cgen` can be used as a CLI tool to generate code using serialized model objects (pickle protocol):
```
$ m2cgen <pickle_file> --language <language> [--indent <indent>] [--class_name <class_name>]
[--module_name <module_name>] [--package_name <package_name>] [--namespace <namespace>]
[--recursion-limit <recursion_limit>]
$ m2cgen <pickle_file> --language <language> [--indent <indent>] [--function_name <function_name>]
[--class_name <class_name>] [--module_name <module_name>] [--package_name <package_name>]
[--namespace <namespace>] [--recursion-limit <recursion_limit>]
```
Don't forget that for unpickling serialized model objects their classes must be defined in the top level of an importable module in the unpickling environment.

Expand Down
55 changes: 37 additions & 18 deletions m2cgen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,26 @@
import pickle
import argparse
import sys
import inspect
import numpy as np

import m2cgen


LANGUAGE_TO_EXPORTER = {
"python": (m2cgen.export_to_python, ["indent"]),
"java": (m2cgen.export_to_java, ["indent", "class_name", "package_name"]),
"c": (m2cgen.export_to_c, ["indent"]),
"go": (m2cgen.export_to_go, ["indent"]),
"javascript": (m2cgen.export_to_javascript, ["indent"]),
"python": (m2cgen.export_to_python, ["indent", "function_name"]),
"java": (m2cgen.export_to_java, ["indent", "class_name", "package_name",
"function_name"]),
"c": (m2cgen.export_to_c, ["indent", "function_name"]),
"go": (m2cgen.export_to_go, ["indent", "function_name"]),
"javascript": (m2cgen.export_to_javascript, ["indent", "function_name"]),
"visual_basic": (m2cgen.export_to_visual_basic,
["module_name", "indent"]),
["module_name", "indent", "function_name"]),
"c_sharp": (m2cgen.export_to_c_sharp,
["indent", "class_name", "namespace"]),
"powershell": (m2cgen.export_to_powershell, ["indent"]),
"r": (m2cgen.export_to_r, ["indent"]),
"php": (m2cgen.export_to_php, ["indent"]),
["indent", "class_name", "namespace", "function_name"]),
"powershell": (m2cgen.export_to_powershell, ["indent", "function_name"]),
"r": (m2cgen.export_to_r, ["indent", "function_name"]),
"php": (m2cgen.export_to_php, ["indent", "function_name"]),
}


Expand All @@ -38,35 +40,42 @@

parser = argparse.ArgumentParser(
prog="m2cgen",
description="Generate code in native language for provided model")
description="Generate code in native language for provided model.")
parser.add_argument(
"infile", type=argparse.FileType("rb"), nargs="?",
default=sys.stdin.buffer,
help="File with pickle representation of the model")
help="File with pickle representation of the model.")
parser.add_argument(
"--language", "-l", type=str,
choices=LANGUAGE_TO_EXPORTER.keys(),
help="Target language",
help="Target language.",
required=True)
parser.add_argument(
"--function_name", "-fn", dest="function_name", type=str,
# The default value is conditional and will be set in the argument's
# post-processing, based on the signature of the `export` function
# that belongs to the specified target language.
default=None,
help="Name of the function in the generated code.")
parser.add_argument(
"--class_name", "-cn", dest="class_name", type=str,
help="Name of the generated class (if supported by target language)")
help="Name of the generated class (if supported by target language).")
parser.add_argument(
"--package_name", "-pn", dest="package_name", type=str,
help="Package name for the generated code "
"(if supported by target language)")
"(if supported by target language).")
parser.add_argument(
"--module_name", "-mn", dest="module_name", type=str,
help="Module name for the generated code "
"(if supported by target language)")
"(if supported by target language).")
parser.add_argument(
"--namespace", "-ns", dest="namespace", type=str,
help="Namespace for the generated code "
"(if supported by target language)")
"(if supported by target language).")
parser.add_argument(
"--indent", "-i", dest="indent", type=int,
default=4,
help="Indentation for the generated code")
help="Indentation for the generated code.")
parser.add_argument(
"--recursion-limit", "-rl", type=int,
help="Sets the maximum depth of the Python interpreter stack. "
Expand Down Expand Up @@ -95,6 +104,16 @@ def generate_code(args):
if arg_value is not None:
kwargs[arg_name] = arg_value

# Special handling for the function_name parameter, which needs to be
# the same as the default value of the keyword argument of the exporter
# (this is due to languages like C# which prefer their method names to
# follow PascalCase unlike all the other supported languages -- see
# https://github.com/BayesWitnesses/m2cgen/pull/166/files#r379867601
# for more).
if arg_name == 'function_name' and arg_value is None:
param = inspect.signature(exporter).parameters['function_name']
kwargs[arg_name] = param.default

return exporter(model, **kwargs)


Expand Down
93 changes: 72 additions & 21 deletions m2cgen/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from m2cgen import interpreters


def export_to_java(model, package_name=None, class_name="Model", indent=4):
def export_to_java(model, package_name=None, class_name="Model", indent=4,
function_name="score"):
"""
Generates a Java code representation of the given model.

Expand All @@ -16,6 +17,8 @@ def export_to_java(model, package_name=None, class_name="Model", indent=4):
The name of the generated class.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
Expand All @@ -24,11 +27,13 @@ def export_to_java(model, package_name=None, class_name="Model", indent=4):
interpreter = interpreters.JavaInterpreter(
package_name=package_name,
class_name=class_name,
indent=indent)
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_python(model, indent=4):
def export_to_python(model, indent=4, function_name="score"):
"""
Generates a Python code representation of the given model.

Expand All @@ -38,16 +43,21 @@ def export_to_python(model, indent=4):
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.PythonInterpreter(indent=indent)
interpreter = interpreters.PythonInterpreter(
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_c(model, indent=4):
def export_to_c(model, indent=4, function_name="score"):
"""
Generates a C code representation of the given model.

Expand All @@ -57,16 +67,21 @@ def export_to_c(model, indent=4):
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.CInterpreter(indent=indent)
interpreter = interpreters.CInterpreter(
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_go(model, indent=4):
def export_to_go(model, indent=4, function_name="score"):
"""
Generates a Go code representation of the given model.

Expand All @@ -76,16 +91,21 @@ def export_to_go(model, indent=4):
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.GoInterpreter(indent=indent)
interpreter = interpreters.GoInterpreter(
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_javascript(model, indent=4):
def export_to_javascript(model, indent=4, function_name="score"):
"""
Generates a JavaScript code representation of the given model.

Expand All @@ -95,16 +115,22 @@ def export_to_javascript(model, indent=4):
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.JavascriptInterpreter(indent=indent)
interpreter = interpreters.JavascriptInterpreter(
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_visual_basic(model, module_name="Model", indent=4):
def export_to_visual_basic(model, module_name="Model", indent=4,
function_name="score"):
"""
Generates a Visual Basic (also can be treated as VBA
with some small manual changes, see a note below)
Expand Down Expand Up @@ -158,17 +184,23 @@ def export_to_visual_basic(model, module_name="Model", indent=4):
The name of the generated module.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.VisualBasicInterpreter(module_name=module_name,
indent=indent)
interpreter = interpreters.VisualBasicInterpreter(
module_name=module_name,
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_c_sharp(model, namespace="ML", class_name="Model", indent=4):
def export_to_c_sharp(model, namespace="ML", class_name="Model", indent=4,
function_name="Score"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

< Here and in all other places >
I believe it can be score for the consistency. Refer to #166 (comment)

I'm not sure that it is worthy of further consideration. As we are still in alpha phase, we are free to change a lot without any deprecation warnings and worrying about the backward compatibility. Especially for just a naming conventions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @StrikerRUS shouldn't we at least comply with basic code style principles of a target language? Like in this case method names are capitalized in C#. I believe this basic logic is worth keeping.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really do not have a strong opinion here, but since C# seems to be using PascalCasing by definition, I would vote for being consistent with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't have a strong opinion on keeping code style for default values too. I just proposed the way which is the easiest to maintain 🙂 .
And then we'll need to apply conditional default values for the CLI part: #166 (comment).

Copy link
Contributor Author

@mrshu mrshu Feb 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@StrikerRUS All right, I went ahead and implemented the "default values for the CLI" part such that they directly use the default value of the function_name keyword argument.

It relies on inspecting the export function (the inspect module introduced in Python 3.5 so that should not be an issue) and might therefore feel a bit hacky, but it allows us to specify the default only once (namely in the keyword argument) and be done with it -- it should automatically down-propagate to the CLI options as well.

I also added a test which should give us a greater confidence that this works.

If you do not like this approach, I am happy to revert that particular commit and just leave it as it was.

Thanks again!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrshu Thanks a lot for implementing conditional default values! I like your approach very much! Just please add a comment about that default value is conditional and will be set later based on the export function signature.

parser.add_argument(
    "--function_name", "-fn", dest="function_name", type=str,
    default=None,  # some comment here 
    help="Name of the function in the generated code.")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @StrikerRUS , I just did that.

Please feel free to point out a simpler version of the comment -- I may have overcomplicated it quite a bit.

"""
Generates a C# code representation of the given model.

Expand All @@ -182,6 +214,8 @@ def export_to_c_sharp(model, namespace="ML", class_name="Model", indent=4):
The name of the generated class.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
Expand All @@ -190,11 +224,13 @@ def export_to_c_sharp(model, namespace="ML", class_name="Model", indent=4):
interpreter = interpreters.CSharpInterpreter(
namespace=namespace,
class_name=class_name,
indent=indent)
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_powershell(model, indent=4):
def export_to_powershell(model, indent=4, function_name="Score"):
"""
Generates a PowerShell code representation of the given model.

Expand All @@ -204,16 +240,21 @@ def export_to_powershell(model, indent=4):
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.PowershellInterpreter(indent=indent)
interpreter = interpreters.PowershellInterpreter(
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_r(model, indent=4):
def export_to_r(model, indent=4, function_name="score"):
"""
Generates a R code representation of the given model.

Expand All @@ -223,16 +264,21 @@ def export_to_r(model, indent=4):
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.RInterpreter(indent=indent)
interpreter = interpreters.RInterpreter(
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


def export_to_php(model, indent=4):
def export_to_php(model, indent=4, function_name="score"):
"""
Generates a PHP code representation of the given model.

Expand All @@ -242,12 +288,17 @@ def export_to_php(model, indent=4):
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.

Returns
-------
code : string
"""
interpreter = interpreters.PhpInterpreter(indent=indent)
interpreter = interpreters.PhpInterpreter(
indent=indent,
function_name=function_name
)
return _export(model, interpreter)


Expand Down
6 changes: 4 additions & 2 deletions m2cgen/interpreters/c/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ class CInterpreter(ToCodeInterpreter,
power_function_name = "pow"
tanh_function_name = "tanh"

def __init__(self, indent=4, *args, **kwargs):
def __init__(self, indent=4, function_name="score", *args, **kwargs):
self.function_name = function_name
mrshu marked this conversation as resolved.
Show resolved Hide resolved

cg = CCodeGenerator(indent=indent)
super(CInterpreter, self).__init__(cg, *args, **kwargs)

Expand All @@ -37,7 +39,7 @@ def interpret(self, expr):
args += [(True, "output")]

with self._cg.function_definition(
name="score",
name=self.function_name,
args=args,
is_scalar_output=expr.output_size == 1):

Expand Down