-
Notifications
You must be signed in to change notification settings - Fork 211
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Major improvements to the std::function<> caster (roundtrips, cyclic …
…GC) (#95) - Roundtrip support! When a Python function has been wrapped in a ``std::function<>``, subsequent conversion to a Python object will return the original Python function object. Note that this is the opposite of pybind11's ``std::function<>`` caster, where roundtrip support is implemented for C function pointers (which isn't possible in nanobind currently, and seems less useful in retrospect). - Building on the previous point, the following C++ snippet ```cpp nb::object o = nb::cast(std_function_instance, nb::rv_policy::none); ``` can be used to attempt a conversion of a ``std::function<>`` instance into a Python object. This will either return the function object or an invalid (``!o.is_valid()``) object if the conversion fails. Why was this added? A useful feature of nanobind is that one can set callback methods on bound C++ instances that redirect control flow back to Python. ```python a = MyCppClass() a.f = lambda x: ... ``` A major potential issue here are reference leaks. What if the lambda function assigned to ``a.f`` captures some variables from the surrounding environment, which in turn reference the instance `a`? Then we have a reference cycle that spans the Python <-> C++ boundary, and that whole set of objects will never be deleted. Fortunately, Python provides a garbage collector that can collect such cycles, but we must provide it with further information so that it can properly do its job. It must be able to traverse the C++ instance to discover contained Python objects. Below is a fully worked out example. ```cpp // Type definition struct MyCppClass { // A callback function that could be implemented in either language std::function<void(void>) f; }; // Traversal method that may be invoked by Python's cyclic GC int mycppclass_tp_traverse(PyObject *self, visitproc visit, void *arg) { MyCppClass *m = nb::cast<MyCppClass *>(nb::handle(self)); if (m) { nb::object f = nb::cast(m->f, nb::rv_policy::none); // If 'f' is a Python function object, then traverse it recursively if (f.is_valid()) Py_VISIT(f.ptr()); } return 0; }; // Callback to register additional type slots in the bindings void mycppclass_type_callback = [](PyType_Slot **s) noexcept { *(*s)++ = { Py_tp_traverse, (void *) mycppclass_tp_traverse }; }; // .. binding code ... nb::class_<MyCppClass>(m, "MyCppClass", nb::type_callback(mycppclass_type_callback)) .def(nb::init<>()) .def_readwrite("f", &FuncWrapper::f); ``` This commit also adds an example of such a cycle to the test suite, which will fail if it cannot be garbage-collected.
- Loading branch information
Showing
5 changed files
with
148 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.