Nicer API to pass py::capsule destructor (#752)

* nicer py::capsule destructor mechanism
* added destructor-only version of capsule & tests
* added documentation for module destructors (fixes #733)
This commit is contained in:
Wenzel Jakob 2017-03-22 22:04:00 +01:00 committed by GitHub
parent ab26259c87
commit b16421edb1
6 changed files with 93 additions and 6 deletions

View File

@ -170,6 +170,20 @@ would be then able to access the data behind the same pointer.
.. [#f6] https://docs.python.org/3/extending/extending.html#using-capsules .. [#f6] https://docs.python.org/3/extending/extending.html#using-capsules
Module Destructors
==================
pybind11 does not provide an explicit mechanism to invoke cleanup code at
module destruction time. In rare cases where such functionality is required, it
is possible to emulate it using Python capsules with a destruction callback.
.. code-block:: cpp
auto cleanup_callback = []() {
// perform cleanup here -- this function is called with the GIL held
};
m.add_object("_cleanup", py::capsule(cleanup_callback));
Generating documentation using Sphinx Generating documentation using Sphinx
===================================== =====================================

View File

@ -235,7 +235,7 @@ handle eigen_ref_array(Type &src, handle parent = none()) {
// not the Type of the pointer given is const. // not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>> template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) { handle eigen_encapsulate(Type *src) {
capsule base(src, [](PyObject *o) { delete static_cast<Type *>(PyCapsule_GetPointer(o, nullptr)); }); capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
return eigen_ref_array<props>(*src, base); return eigen_ref_array<props>(*src, base);
} }

View File

@ -294,8 +294,8 @@ protected:
rec->def->ml_meth = reinterpret_cast<PyCFunction>(*dispatcher); rec->def->ml_meth = reinterpret_cast<PyCFunction>(*dispatcher);
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
capsule rec_capsule(rec, [](PyObject *o) { capsule rec_capsule(rec, [](void *ptr) {
destruct((detail::function_record *) PyCapsule_GetPointer(o, nullptr)); destruct((detail::function_record *) ptr);
}); });
object scope_module; object scope_module;

View File

@ -1004,10 +1004,44 @@ public:
PYBIND11_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact) PYBIND11_OBJECT_DEFAULT(capsule, object, PyCapsule_CheckExact)
PYBIND11_DEPRECATED("Use reinterpret_borrow<capsule>() or reinterpret_steal<capsule>()") PYBIND11_DEPRECATED("Use reinterpret_borrow<capsule>() or reinterpret_steal<capsule>()")
capsule(PyObject *ptr, bool is_borrowed) : object(is_borrowed ? object(ptr, borrowed) : object(ptr, stolen)) { } capsule(PyObject *ptr, bool is_borrowed) : object(is_borrowed ? object(ptr, borrowed) : object(ptr, stolen)) { }
explicit capsule(const void *value, void (*destruct)(PyObject *) = nullptr)
: object(PyCapsule_New(const_cast<void*>(value), nullptr, destruct), stolen) { explicit capsule(const void *value)
if (!m_ptr) pybind11_fail("Could not allocate capsule object!"); : object(PyCapsule_New(const_cast<void *>(value), nullptr, nullptr), stolen) {
if (!m_ptr)
pybind11_fail("Could not allocate capsule object!");
} }
PYBIND11_DEPRECATED("Please pass a destructor that takes a void pointer as input")
capsule(const void *value, void (*destruct)(PyObject *))
: object(PyCapsule_New(const_cast<void*>(value), nullptr, destruct), stolen) {
if (!m_ptr)
pybind11_fail("Could not allocate capsule object!");
}
capsule(const void *value, void (*destructor)(void *)) {
m_ptr = PyCapsule_New(const_cast<void *>(value), nullptr, [](PyObject *o) {
auto destructor = reinterpret_cast<void (*)(void *)>(PyCapsule_GetContext(o));
void *ptr = PyCapsule_GetPointer(o, nullptr);
destructor(ptr);
});
if (!m_ptr)
pybind11_fail("Could not allocate capsule object!");
if (PyCapsule_SetContext(m_ptr, (void *) destructor) != 0)
pybind11_fail("Could not set capsule context!");
}
capsule(void (*destructor)()) {
m_ptr = PyCapsule_New(reinterpret_cast<void *>(destructor), nullptr, [](PyObject *o) {
auto destructor = reinterpret_cast<void (*)()>(PyCapsule_GetPointer(o, nullptr));
destructor();
});
if (!m_ptr)
pybind11_fail("Could not allocate capsule object!");
}
template <typename T> operator T *() const { template <typename T> operator T *() const {
T * result = static_cast<T *>(PyCapsule_GetPointer(m_ptr, nullptr)); T * result = static_cast<T *>(PyCapsule_GetPointer(m_ptr, nullptr));
if (!result) pybind11_fail("Unable to extract capsule contents!"); if (!result) pybind11_fail("Unable to extract capsule contents!");

View File

@ -470,6 +470,24 @@ test_initializer python_types([](py::module &m) {
m.def("return_none_bool", []() -> bool * { return nullptr; }); m.def("return_none_bool", []() -> bool * { return nullptr; });
m.def("return_none_int", []() -> int * { return nullptr; }); m.def("return_none_int", []() -> int * { return nullptr; });
m.def("return_none_float", []() -> float * { return nullptr; }); m.def("return_none_float", []() -> float * { return nullptr; });
m.def("return_capsule_with_destructor",
[]() {
py::print("creating capsule");
return py::capsule([]() {
py::print("destructing capsule");
});
}
);
m.def("return_capsule_with_destructor_2",
[]() {
py::print("creating capsule");
return py::capsule((void *) 1234, [](void *ptr) {
py::print("destructing capsule: {}"_s.format((size_t) ptr));
});
}
);
}); });
#if defined(_MSC_VER) #if defined(_MSC_VER)

View File

@ -512,3 +512,24 @@ def test_builtins_cast_return_none():
assert m.return_none_bool() is None assert m.return_none_bool() is None
assert m.return_none_int() is None assert m.return_none_int() is None
assert m.return_none_float() is None assert m.return_none_float() is None
def test_capsule_with_destructor(capture):
import pybind11_tests as m
with capture:
a = m.return_capsule_with_destructor()
del a
pytest.gc_collect()
assert capture.unordered == """
creating capsule
destructing capsule
"""
with capture:
a = m.return_capsule_with_destructor_2()
del a
pytest.gc_collect()
assert capture.unordered == """
creating capsule
destructing capsule: 1234
"""