3 GTSAM Copyright 2010-2020, Georgia Tech Research Corporation, 4 Atlanta, Georgia 30332-0415 7 See LICENSE for the license information 9 Code generator for wrapping a C++ module with Pybind11 10 Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert 16 from pathlib
import Path
17 from typing
import List
25 Class to generate binding code for Pybind11 specifically. 30 top_module_namespaces='',
31 use_boost_serialization=False,
41 'lambda',
'False',
'def',
'if',
'raise',
'None',
'del',
'import',
42 'return',
'True',
'elif',
'in',
'try',
'and',
'else',
'is',
43 'while',
'as',
'except',
'lambda',
'with',
'assert',
'finally',
44 'nonlocal',
'yield',
'break',
'for',
'not',
'class',
'from',
'or',
45 'continue',
'global',
'pass' 53 "svg",
"png",
"jpeg",
"html",
"javascript",
"markdown",
"latex" 57 """Set the argument names in Pybind11 format.""" 61 for arg
in args.list():
62 if arg.default
is not None:
63 default =
' = {arg.default}'.
format(arg=arg)
66 argument =
'py::arg("{name}"){default}'.
format(
67 name=arg.name, default=
'{0}'.
format(default))
68 py_args.append(argument)
69 return ", " +
", ".join(py_args)
74 """Generate the argument types and names as per the method signature.""" 75 cpp_types = args.to_cpp()
78 "{} {}".
format(ctype, name)
79 for ctype, name
in zip(cpp_types, names)
82 return ', '.join(types_names)
85 """Wrap the constructors.""" 87 for ctor
in my_class.ctors:
88 res += (self.
method_indent +
'.def(py::init<{args_cpp_types}>()' 90 args_cpp_types=
", ".join(ctor.args.to_cpp()),
96 """Helper method to add serialize, deserialize and pickle methods to the wrapped class.""" 101 ".def(\"serialize\", []({class_inst} self){{ return gtsam::serialize(*self); }})".
format(class_inst=cpp_class +
'*')
104 '.def("deserialize", []({class_inst} self, string serialized)' \
105 '{{ gtsam::deserialize(serialized, *self); }}, py::arg("serialized"))' \
106 .
format(class_inst=cpp_class +
'*')
110 ".def(py::pickle({indent} [](const {cpp_class} &a){{ /* __getstate__: Returns a string that encodes the state of the object */ return py::make_tuple(gtsam::serialize(a)); }},{indent} [](py::tuple t){{ /* __setstate__ */ {cpp_class} obj; gtsam::deserialize(t[0].cast<std::string>(), obj); return obj; }}))" 112 return serialize_method + deserialize_method + \
113 pickle_method.format(cpp_class=cpp_class, indent=self.
method_indent)
115 def _wrap_print(self, ret: str, method: parser.Method, cpp_class: str,
116 args_names: List[str], args_signature_with_names: str,
117 py_args_names: str, prefix: str, suffix: str):
119 Update the print method to print to the output stream and append a __repr__ method. 122 ret (str): The result of the parser. 123 method (parser.Method): The method to be wrapped. 124 cpp_class (str): The C++ name of the class to which the method belongs. 125 args_names (List[str]): List of argument variable names passed to the method. 126 args_signature_with_names (str): C++ arguments containing their names and type signatures. 127 py_args_names (str): The pybind11 formatted version of the argument list. 128 prefix (str): Prefix to add to the wrapped method when writing to the cpp file. 129 suffix (str): Suffix to add to the wrapped method when writing to the cpp file. 132 str: The wrapped print method. 136 ret = ret.replace(
'self->print',
137 'py::scoped_ostream_redirect output; self->print')
140 ret +=
'''{prefix}.def("__repr__", 141 [](const {cpp_class}& self{opt_comma}{args_signature_with_names}){{ 142 gtsam::RedirectCout redirect; 143 self.{method_name}({method_args}); 144 return redirect.str(); 145 }}{py_args_names}){suffix}'''.
format(
148 opt_comma=
', ' if args_names
else '',
149 args_signature_with_names=args_signature_with_names,
150 method_name=method.name,
151 method_args=
", ".join(args_names)
if args_names
else '',
152 py_args_names=py_args_names,
163 Wrap the `method` for the class specified by `cpp_class`. 166 method: The method to wrap. 167 cpp_class: The C++ name of the class to which the method belongs. 168 prefix: Prefix to add to the wrapped method when writing to the cpp file. 169 suffix: Suffix to add to the wrapped method when writing to the cpp file. 170 method_suffix: A string to append to the wrapped method name. 172 py_method = method.name + method_suffix
173 cpp_method = method.to_cpp()
175 args_names = method.args.names()
180 if cpp_method
in [
"serialize",
"serializable"]:
190 py_method = f
"_repr_{self._ipython_special_methods[idx]}_" 194 py_method = py_method +
"_" 197 method, (parser.Method, instantiator.InstantiatedMethod))
200 (parser.StaticMethod, instantiator.InstantiatedStaticMethod))
201 return_void = method.return_type.is_void()
203 caller = cpp_class +
"::" if not is_method
else "self->" 204 function_call = (
'{opt_return} {caller}{method_name}' 206 opt_return=
'return' if not return_void
else '',
208 method_name=cpp_method,
209 args_names=
', '.join(args_names),
212 ret = (
'{prefix}.{cdef}("{py_method}",' 213 '[]({opt_self}{opt_comma}{args_signature_with_names}){{' 216 '{py_args_names}){suffix}'.
format(
218 cdef=
"def_static" if is_static
else "def",
220 opt_self=
"{cpp_class}* self".
format(
221 cpp_class=cpp_class)
if is_method
else "",
222 opt_comma=
', ' if is_method
and args_names
else '',
223 args_signature_with_names=args_signature_with_names,
224 function_call=function_call,
225 py_args_names=py_args_names,
231 if method.name ==
'print':
232 ret = self.
_wrap_print(ret, method, cpp_class, args_names,
233 args_signature_with_names, py_args_names,
241 prefix='\n' + ' ' * 8,
244 Wrap all the methods in the `cpp_class`. 247 for method
in methods:
250 if method.name ==
'insert' and cpp_class ==
'gtsam::Values':
251 name_list = method.args.names()
252 type_list = method.args.to_cpp()
254 if type_list[0].strip() ==
'size_t':
255 method_suffix =
'_' + name_list[1].strip()
260 method_suffix=method_suffix)
275 prefix='\n' + ' ' * 8):
277 Wrap a variable that's not part of a class (i.e. global) 280 if variable.default
is None:
281 variable_value = variable.name
283 variable_value = variable.default
285 return '{prefix}{module_var}.attr("{variable_name}") = {namespace}{variable_value};'.
format(
287 module_var=module_var,
288 variable_name=variable.name,
290 variable_value=variable_value)
293 """Wrap all the properties in the `cpp_class`.""" 295 for prop
in properties:
296 res += (
'{prefix}.def_{property}("{property_name}", ' 297 '&{cpp_class}::{property_name})'.
format(
300 if prop.ctype.is_const
else "readwrite",
302 property_name=prop.name,
307 """Wrap all the overloaded operators in the `cpp_class`.""" 309 template =
"{prefix}.def({{0}})".
format(prefix=prefix)
311 if op.operator ==
"[]":
312 res +=
"{prefix}.def(\"__getitem__\", &{cpp_class}::operator[])".
format(
313 prefix=prefix, cpp_class=cpp_class)
314 elif op.operator ==
"()":
315 res +=
"{prefix}.def(\"__call__\", &{cpp_class}::operator())".
format(
316 prefix=prefix, cpp_class=cpp_class)
318 res += template.format(
"{0}py::self".
format(op.operator))
320 res += template.format(
"py::self {0} py::self".
format(
324 def wrap_enum(self, enum, class_name='', module=None, prefix=' ' * 4):
329 enum: The parsed enum to wrap. 330 class_name: The class under which the enum is defined. 331 prefix: The amount of indentation. 336 cpp_class = enum.cpp_typename().to_cpp()
339 cpp_class = class_name +
"::" + cpp_class
341 res =
'{prefix}py::enum_<{cpp_class}>({module}, "{enum.name}", py::arithmetic())'.
format(
342 prefix=prefix, module=module, enum=enum, cpp_class=cpp_class)
343 for enumerator
in enum.enumerators:
344 res +=
'\n{prefix} .value("{enumerator.name}", {cpp_class}::{enumerator.name})'.
format(
345 prefix=prefix, enumerator=enumerator, cpp_class=cpp_class)
349 def wrap_enums(self, enums, instantiated_class, prefix=' ' * 4):
350 """Wrap multiple enums defined in a class.""" 351 cpp_class = instantiated_class.to_cpp()
352 module_var = instantiated_class.name.lower()
357 enum, class_name=cpp_class, module=module_var, prefix=prefix)
361 self, instantiated_class: instantiator.InstantiatedClass):
362 """Wrap the class.""" 364 cpp_class = instantiated_class.to_cpp()
367 if instantiated_class.parent_class:
368 class_parent =
"{instantiated_class.parent_class}, ".
format(
369 instantiated_class=instantiated_class)
373 if instantiated_class.enums:
375 instance_name = instantiated_class.name.lower()
376 class_declaration = (
377 '\n py::class_<{cpp_class}, {class_parent}' 378 'std::shared_ptr<{cpp_class}>> ' 379 '{instance_name}({module_var}, "{class_name}");' 380 '\n {instance_name}').
format(
382 class_name=instantiated_class.name,
383 class_parent=class_parent,
384 instance_name=instance_name,
385 module_var=module_var)
386 module_var = instance_name
389 class_declaration = (
390 '\n py::class_<{cpp_class}, {class_parent}' 391 'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}")' 392 ).
format(cpp_class=cpp_class,
393 class_name=instantiated_class.name,
394 class_parent=class_parent,
395 module_var=module_var)
397 return (
'{class_declaration}' 400 '{wrapped_static_methods}' 401 '{wrapped_properties}' 402 '{wrapped_operators};\n'.
format(
403 class_declaration=class_declaration,
404 wrapped_ctors=self.
wrap_ctors(instantiated_class),
406 instantiated_class.methods, cpp_class),
408 instantiated_class.static_methods, cpp_class),
410 instantiated_class.properties, cpp_class),
412 instantiated_class.operators, cpp_class)))
415 self, instantiated_decl: instantiator.InstantiatedDeclaration):
416 """Wrap the forward declaration.""" 418 cpp_class = instantiated_decl.to_cpp()
422 res = (
'\n py::class_<{cpp_class}, ' 423 'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}");' 424 ).
format(cpp_class=cpp_class,
425 class_name=instantiated_decl.name,
426 module_var=module_var)
430 """Wrap STL containers.""" 432 cpp_class = stl_class.to_cpp()
436 return (
'\n py::class_<{cpp_class}, {class_parent}' 437 'std::shared_ptr<{cpp_class}>>({module_var}, "{class_name}")' 440 '{wrapped_static_methods}' 441 '{wrapped_properties};\n'.
format(
443 class_name=stl_class.name,
444 class_parent=
str(stl_class.parent_class) +
445 (
', ' if stl_class.parent_class
else ''),
446 module_var=module_var,
451 stl_class.static_methods, cpp_class),
453 stl_class.properties, cpp_class),
459 prefix='\n' + ' ' * 8,
462 Wrap all the global functions. 465 for function
in functions:
467 function_name = function.name
471 if function_name
in python_keywords:
472 function_name = function_name +
"_" 474 cpp_method = function.to_cpp()
476 is_static =
isinstance(function, parser.StaticMethod)
477 return_void = function.return_type.is_void()
478 args_names = function.args.names()
482 caller = namespace +
"::" 483 function_call = (
'{opt_return} {caller}{function_name}' 486 if not return_void
else '',
488 function_name=cpp_method,
489 args_names=
', '.join(args_names),
492 ret = (
'{prefix}.{cdef}("{function_name}",' 493 '[]({args_signature}){{' 496 '{py_args_names}){suffix}'.
format(
498 cdef=
"def_static" if is_static
else "def",
499 function_name=function_name,
500 args_signature=args_signature,
501 function_call=function_call,
502 py_args_names=py_args_names,
511 if namespaces1[i] != namespaces2[i]:
516 """Get the Pybind11 module name from the namespaces.""" 519 return "m_{}".
format(
'_'.join(sub_module_namespaces))
524 idx = 1
if not namespaces[0]
else 0
525 return '::'.join(namespaces[idx:] + [name])
530 """Wrap the complete `namespace`.""" 534 namespaces = namespace.full_namespaces()
539 for element
in namespace.content:
541 include =
"{}\n".
format(element)
543 include = include.replace(
'<',
'"').replace(
'>',
'"')
551 wrapped += wrapped_namespace
552 includes += includes_namespace
558 ' ' * 4 +
'pybind11::module {module_var} = ' 559 '{parent_module_var}.def_submodule("{namespace}", "' 560 '{namespace} submodule");\n'.
format(
561 module_var=module_var,
562 namespace=namespace.name,
568 for element
in namespace.content:
570 include =
"{}\n".
format(element)
572 include = include.replace(
'<',
'"').replace(
'>',
'"')
577 wrapped += wrapped_namespace
578 includes += includes_namespace
580 elif isinstance(element, instantiator.InstantiatedClass):
582 wrapped += self.
wrap_enums(element.enums, element)
584 elif isinstance(element, instantiator.InstantiatedDeclaration):
590 module_var=module_var,
592 prefix=
'\n' +
' ' * 4)
599 func
for func
in namespace.content
601 instantiator.InstantiatedGlobalFunction))
606 prefix=
'\n' +
' ' * 4 + module_var,
610 return wrapped, includes
612 def wrap_file(self, content, module_name=None, submodules=None):
614 Wrap the code in the interface file. 617 content: The contents of the interface file. 618 module_name: The name of the module. 619 submodules: List of other interface file names that should be linked to. 622 module = parser.Module.parseString(content)
624 module = instantiator.instantiate_namespace(module)
629 includes +=
"#include <boost/serialization/export.hpp>" 632 boost_class_export =
"" 637 new_name = re.sub(
"[,:<> ]",
"", cpp_class)
638 boost_class_export +=
"typedef {cpp_class} {new_name};\n".
format(
639 cpp_class=cpp_class, new_name=new_name)
641 boost_class_export +=
"BOOST_CLASS_EXPORT({new_name})\n".
format(
644 boost_class_export =
"" 651 if submodules
is not None:
652 module_def =
"PYBIND11_MODULE({0}, m_)".
format(module_name)
654 for idx, submodule
in enumerate(submodules):
655 submodules[idx] =
"void {0}(py::module_ &);".
format(submodule)
656 submodules_init.append(
"{0}(m_);".
format(submodule))
659 module_def =
"void {0}(py::module_ &m_)".
format(module_name)
663 module_def=module_def,
664 module_name=module_name,
666 wrapped_namespace=wrapped_namespace,
667 boost_class_export=boost_class_export,
668 submodules=
"\n".join(submodules),
669 submodules_init=
"\n".join(submodules_init),
674 Wrap a list of submodule files, i.e. a set of interface files which are 675 in support of a larger wrapping project. 677 E.g. This is used in GTSAM where we have a main gtsam.i, but various smaller .i files 678 which are the submodules. 679 The benefit of this scheme is that it reduces compute and memory usage during compilation. 682 source: Interface file which forms the submodule. 684 filename = Path(source).name
685 module_name = Path(source).stem
688 with open(source,
"r", encoding="UTF-8") as f:
691 cc_content = self.
wrap_file(content, module_name=module_name)
694 with open(filename.replace(
".i",
".cpp"),
"w", encoding=
"UTF-8")
as f:
697 def wrap(self, sources, main_module_name):
699 Wrap all the main interface file. 702 sources: List of all interface files. 703 The first file should be the main module. 704 main_module_name: The name for the main module. 706 main_module = sources[0]
710 for source
in sources[1:]:
711 module_name = Path(source).stem
712 submodules.append(module_name)
714 with open(main_module,
"r", encoding="UTF-8") as f:
718 submodules=submodules)
721 with open(main_module_name,
"w", encoding=
"UTF-8")
as f:
def wrap_properties(self, properties, cpp_class, prefix='\n'+' ' *8)
def wrap_instantiated_declaration
def wrap_instantiated_class
def _gen_module_var(self, namespaces)
def wrap_functions(self, functions, namespace, prefix='\n'+' ' *8, suffix='')
def wrap_ctors(self, my_class)
def __init__(self, module_name, top_module_namespaces='', use_boost_serialization=False, ignore_classes=(), module_template="")
def wrap_methods(self, methods, cpp_class, prefix='\n'+' ' *8, suffix='')
def wrap_enum(self, enum, class_name='', module=None, prefix=' ' *4)
bool isinstance(handle obj)
def _partial_match(self, namespaces1, namespaces2)
def wrap_stl_class(self, stl_class)
def wrap_enums(self, enums, instantiated_class, prefix=' ' *4)
def wrap_file(self, content, module_name=None, submodules=None)
def wrap_namespace(self, namespace)
def _wrap_serialization(self, cpp_class)
def wrap_variable(self, namespace, module_var, variable, prefix='\n'+' ' *8)
def _add_namespaces(self, name, namespaces)
def wrap_submodule(self, source)
def wrap(self, sources, main_module_name)
Double_ range(const Point2_ &p, const Point2_ &q)
std::string format(const std::string &str, const std::vector< std::string > &find, const std::vector< std::string > &replace)
def _py_args_names(self, args)
def wrap_operators(self, operators, cpp_class, prefix='\n'+' ' *8)
size_t len(handle h)
Get the length of a Python object.
def _wrap_method(self, method, cpp_class, prefix, suffix, method_suffix="")
def _method_args_signature(self, args)