Details on implementing python bindings for the C++ code.
Drake uses pybind11 for binding its C++ API to Python.
At present, a fork of pybind11
is used which permits bindings matrices with dtype=object
, passing unique_ptr
objects, and prevents aliasing for Python classes derived from pybind11
classes.
Before delving too deep into this, please first review the user-facing documentation about What's Available from Python.
The structure of the bindings generally follow the directory structure, not the namespace structure. As an example, the following code in C++:
will look similar in Python, but you won't use the header file's name:
In general, you can find where a symbol is bound by searching for the symbol's name in quotes underneath the directory drake/bindings/pydrake
.
For example, you should search for "MultibodyPlant"
, "Parser"
, etc. To elaborate, the binding of Parser
is found in .../pydrake/multibody/parsing_py.cc
, and looks like this:
and binding of MultibodyPlant
template instantiations are in .../pydrake/multibody/plant_py.cc
and look like this:
where the function containing this definition is templated on type T
and invoked for the scalar types mentioned in drake/common/default_scalars.h
.
Some (but not all) exceptions to the above rules:
drake/common/autodiff.h
symbols live in pydrake.autodiffutils
.drake/common/symbolic.h
symbols live in pydrake.symbolic
.drake/common/value.h
symbols live in pydrake.common.value
.drake/systems/framework/*.h
symbols live in pydrake.systems.framework
(per the rule) and the bindings are ultimately linked via pydrake/systems/framework_py.cc
, but for compilation speed the binding definitions themselves are split into ./framework_py_{semantics,systems,values}.cc
.pybind11
TipsThroughout the Drake code, Python types provided by pybind11
are used, such as py::handle
, py::object
, py::module
, py::str
, py::list
, etc. For an overview, see the pybind11 reference.
All of these are effectively thin wrappers around PyObject*
, and thus can be cheaply copied.
Mutating the referred-to object also does not require passing by reference, so you can always pass the object by value for functions, but you should document your method if it mutates the object in a non-obvious fashion.
You can implicit convert between py::object
and its derived classes (such as py::list
, py::class_
, etc.), assuming the actual Python types agree. You may also implicitly convert from py::object
(and its derived classes) to py::handle
.
If you wish to convert a py::handle
(or PyObject*
) to py::object
or a derived class, you should use py::reinterpret_borrow<>
.
Any Python bindings of C++ code will maintain C++ naming conventions, as well as Python code that is directly related to C++ symbols (e.g. shims, wrappers, or extensions on existing bound classes).
All other Python code be Pythonic and use PEP 8 naming conventions.
For binding functions or methods, argument names should be provided that correspond exactly to the C++ signatures using py::arg("arg_name")
. This permits the C++ documentation to be relevant to the Sphinx-generated documentation, and allows for the keyword-arguments to be used in Python.
For binding functions, methods, properties, and classes, docstrings should be provided. These should be provided as described here.
In general, since the Python bindings wrap tested C++ code, you do not (and should not) repeat intricate testing logic done in C++. Instead, ensure you exercise the Pythonic portion of the API, using kwargs when appropriate.
When testing the values of NumPy matrices, please review the documentation in pydrake.common.test_utilities.numpy_compare
for guidance.
*_py
: A Python library (can be pure Python or pybind)*.py
, *_py.cc
*_py.cc
file should only define one package (a module; optionally with multiple submodules under it).*_pybind
: A C++ library for adding pybind-specific utilities to be consumed by C++.*_pybind.{h,cc}
File names should follow form with their respective target.
pydrake
will be consumed as one encapsulated target.*_pybind
libraries for binding utilities should be public to aide downstream Bazel projects. If the API is unstable, consider making it private with a TODO to make public once it stabilizes.Given that libdrake.so
relies on static linking for components, any common headers should be robust against ODR violations. This can be normally achieved by using header-only libraries.
For upstream dependencies of these libraries, do NOT depend on the direct targets (e.g. //common:essential
), because this will introduce runtime ODR violations for objects that have static storage (UID counters, etc.).
Instead, you must temporarily violate IWYU because it will be satisfied by drake_pybind_library
, which will incorporate libdrake.so
and the transitive headers.
If singletons are required (e.g. for util/cpp_param_pybind
), consider storing the singleton values using Python.
If you are developing bindings for a small portion of Drake and would like to avoid rebuilding a large number of components when testing, consider editing //tools/install/libdrake:build_components.bzl
to reduce the number of components being built.
pydrake/geometry/**
or pydrake/visualization/**
), but here's a summary of the rules:pydrake_pybind.h
.PYBIND_MODULE
should be used to define modules.namespace py = pybind11
is defined as drake::pydrake::py
. Drake modules should not re-define this alias at global scope.drake::systems::sensors
), you may use using namespace drake::systems::sensors
within functions or anonymous namespaces. Avoid using namespace
directives otherwise.py::module::import("pydrake.foo"))
). Failure to do so can cause errors (unable to cast unregistered types from Python to C++) and can cause the generated docstring from pybind11 to render these types by their C++ typeid
rather than the Python type name.py_deps
attribute for the drake_pybind_library()
target._py.cc
, ensure that you tell Python to import dependency bindings. This is important to load the bindings at the right time (for documentation), and ensure the dependent bindings are executed so that users can import that module in isolation and still have it work. This is important when your type signatures use dependency bindings, or a class declares its inheritance, etc.# .../my_component.h #include "drake/geometry/meshcat.h" void MyMethod(std::shared_ptr<Meshcat> meshcat); # bindings/.../BUILD.bazel drake_pybind_library( name = "my_method_py", ... py_deps = [ ... "//bindings/pydrake/geometry", ], ... ) # bindings/.../my_method_py.cc PYBIND_MODULE(my_method, m) { py::module::import("pydrake.geometry"); m.def("MyMethod", &MyMethod, ...); }
Drake uses a modified version of mkdoc.py
from pybind11
, where libclang
Python bindings are used to generate C++ docstrings accessible to the C++ binding code.
These docstrings are available within constexpr struct ... pydrake_doc
as const char*
values . When these are not available or not suitable for Python documentation, provide custom strings. If this custom string is long, consider placing them in a heredoc string.
An example of incorporating docstrings from pydrake_doc
:
An example of supplying custom strings:
To view the documentation rendered in Sphinx:
bazel run //doc/pydrake:serve_sphinx [-- --browser=false]
To browse the generated documentation strings that are available for use (or especially, to find out the names for overloaded functions' documentation), generate and open the docstring header:
bazel build //bindings/pydrake:documentation_pybind.h $EDITOR bazel-bin/bindings/pydrake/documentation_pybind.h
Search the comments for the symbol of interest, e.g., drake::math::RigidTransform::RigidTransform<T>
, and view the include file and line corresponding to the symbol that the docstring was pulled from.
mkdoc.py
to focus only on your include file of chioce. As an example, debugging mathematical_program.h
: For more detail:
documentation_pybind.h
in the nested structure pydrake_doc
.pydrake_doc.drake.{namespace...}.{symbol}.doc
..doc_something
instead of just .doc
, where the _something
suffix conveys some information about the overload. Browse the documentation_pybind.h (described above) for details. Most commonly, the names will be doc_1args
, doc_3args
, etc. Be sure that the pydrake binding's signature is consistent with the docstring argument count.@exclude_from_pydrake_mkdoc{Explanation}
to the API comment text. This is useful to help dismiss unbound overloads, so that mkdoc's choice of _something
name suffix is simpler for the remaining overloads, especially if you see the symbol .doc_was_unable_to_choose_unambiguous_names
in the generated documentation..doc_foobar
identifier name, add the line @pydrake_mkdoc_identifier{foobar}
to the Doxygen comment..doc_deprecated...
instead of just .doc...
.Decorators and utilities for deprecation in pure Python are available in pydrake.common.deprecation
.
Deprecations for Python bindings in C++ are available in drake/bindings/pydrake/common/deprecation_pybind.h
.
For examples of how to use the deprecations and what side effects they will have, please see:
warnings
module, and the DrakeDeprecationWarning
class. The utilities mentioned above use them.py::keep_alive<Nurse, Patient>()
is used heavily throughout this code. Please first review the pybind11 documentation.
py::keep_alive
decorations should be added after all py::arg
s are specified. Terse comments should be added above these decorations to indicate the relationship between the Nurse and the Patient and decode the meaning of the Nurse and Patient integers by spelling out either the py::arg
name (for named arguments), return
for index 0, or self
(not this
) for index 1 when dealing with methods / members. The primary relationships:
DiagramBuilder.Build()
), append (tr.)
to the relationship.Some example comments:
For more information about pybind11
return value policies, see the pybind11 documentation.
pydrake
offers the py_rvp alias to help with shortened usage of py::return_value_policy
. The most used (non-default) policies in pydrake
are reference
and reference_internal
due to the usage of raw pointers / references in the public C++ API (rather than std::shared_ptr<>
).
py_rvp::reference_internal
effectively implies py_rvp::reference
and py::keep_alive<0, 1>()
, we choose to only use it when self
is the intended patient (i.e. the bound item is a class method). For static / free functions, we instead explicitly spell out py_rvp::reference
and py::keep_alive<0, 1>()
.To bind function overloads, please try the following (in order):
py::overload_cast<Args>(func)
: See the pybind11 documentation. This works about 80% of the time.pydrake::overload_cast_explicit<Return, Args...>(func)
: When py::overload_cast
does not work (not always guaranteed to work).static_cast
, as mentioned in the pybind11 documentation.[](Args... args) -> auto&& { return func(args...); }
(using perfect forwarding when appropriate).const
Method OverloadsC++ has the ability to distinguish T
and const T
for both function arguments and class methods. However, Python does not have a native mechanism for this. It is possible to provide a feature like this in Python (see discussion and prototypes in #7793); however, its pros (similarity to C++) have not yet outweighted the cons (awkward non-Pythonic types and workflows).
When a function is overloaded only by its const
-ness, choose to bind the mutable overload, not the const
overload.
We do this because pybind11
evaluates overloads in the order they are bound, and is not possible for a user to "reach" any overloads that can only be disambiguated by const-ness.
Examples of functions to bind and not bind:
The motivation behind this section can be found under the "C++ Function and Method Template Instantiations in Python" section in doc/python_bindings.rst
.
In general, Drake uses techniques like parameter packs and type erasure to create sugar functions. These functions map their inputs to parameters of some concrete, under-the-hood method that actually does the work, and is devoid of such tricks. To facilitate python bindings, this underlying function should also be exposed in the public API.
As an example for parameter packs, MultibodyPlant<T>::AddJoint<JointType, Args...>(...)
(code permalink) is a C++ sugar method that uses parameter packs and ultimately passes the result to MultibodyPlant<T>::AddJoint<JointType>(unique_ptr<JointType>)
(code permalink), and only the unique_ptr
function is bound (code permalink):
As an example for parameter packs, GeometryProperties::AddProperty<ValueType>
(code permalink) is a C++ sugar method that uses type erasure and ultimately passes the result to GeometryProperties::AddPropertyAbstract
(code permalink), and only the AddPropertyAbstract
flavor is used in the bindings, but in such a way that it is similar to the C++ API for AddProperty
(code permalink):
For objects that may be represented by matrices or vectors (e.g. RigidTransform, RotationMatrix), the *
operator (via __mul__
) should not be bound because the *
operator in NumPy implies elemnt-wise multiplication for arrays.
For simplicity, we instead bind the explicitly named .multiply()
method, and alias the __matmul__
operator @
to this function.
If you wish to bind a Clone()
method, please use DefClone()
so that __copy__()
and __deepcopy__()
will also be defined. (It is simplest to do these things together.)
Certain bound methods, like RigidTransform.multiply()
, will have overloads that can multiply and return (a) other RigidTransform
instances, (b) vectors, or (c) matrices (representing a list of vectors).
In the cases of (a) and (c), pybind11
provides sufficient mechanisms to provide an unambiguous output return type. However, for (b), pybind11
will return ndarray
with shape (3,)
. This can cause an issue when users pass a vector of shape (3, 1)
as input. Nominally, pybind11 will return a (3,)
array, but the user may expect (3, 1)
as an output. To accommodate this, you should use the drake::pydrake::WrapToMatchInputShape function.
In general, minimize the amount in which users may subclass C++ classes in Python. When you do wish to do this, ensure that you use a trampoline class in pybind
, and ensure that the trampoline class inherits from the py::wrapper<>
class specific to our fork of pybind
. This ensures that no slicing happens with the subclassed instances.
If you are debugging a unitest, first try running the test with --trace=user
to see where the code is failing. This should cover most cases where you need to debug C++ bits. Example:
bazel run //bindings/pydrake/systems:py/lifetime_test -- --trace=user
If you need to debug further while using Bazel, it is suggested to use gdbserver
for simplicity. Example:
set sysroot
is important for using gdbserver
, set breakpoint pending on
allows you set the breakpoints before loading libdrake.so
, and dir ...
adds source directories. It is also suggested that you enable readline history in ~/.gdbinit
for ease of use.
If using CLion, you can still connect to the gdbserver
instance.
There are analogs for lldb
/ lldbserver
but for brevity, only GDB is covered.