mirror of
https://github.com/pybind/pybind11.git
synced 2025-02-16 13:47:53 +00:00
Add a scope guard call policy
```c++ m.def("foo", foo, py::call_guard<T>()); ``` is equivalent to: ```c++ m.def("foo", [](args...) { T scope_guard; return foo(args...); // forwarded arguments }); ```
This commit is contained in:
parent
83a8a977a7
commit
1ac19036d6
@ -162,14 +162,16 @@ Additional call policies
|
|||||||
========================
|
========================
|
||||||
|
|
||||||
In addition to the above return value policies, further *call policies* can be
|
In addition to the above return value policies, further *call policies* can be
|
||||||
specified to indicate dependencies between parameters. In general, call policies
|
specified to indicate dependencies between parameters or ensure a certain state
|
||||||
are required when the C++ object is any kind of container and another object is being
|
for the function call.
|
||||||
added to the container.
|
|
||||||
|
|
||||||
There is currently just
|
Keep alive
|
||||||
one policy named ``keep_alive<Nurse, Patient>``, which indicates that the
|
----------
|
||||||
argument with index ``Patient`` should be kept alive at least until the
|
|
||||||
argument with index ``Nurse`` is freed by the garbage collector. Argument
|
In general, this policy is required when the C++ object is any kind of container
|
||||||
|
and another object is being added to the container. ``keep_alive<Nurse, Patient>``
|
||||||
|
indicates that the argument with index ``Patient`` should be kept alive at least
|
||||||
|
until the argument with index ``Nurse`` is freed by the garbage collector. Argument
|
||||||
indices start at one, while zero refers to the return value. For methods, index
|
indices start at one, while zero refers to the return value. For methods, index
|
||||||
``1`` refers to the implicit ``this`` pointer, while regular arguments begin at
|
``1`` refers to the implicit ``this`` pointer, while regular arguments begin at
|
||||||
index ``2``. Arbitrarily many call policies can be specified. When a ``Nurse``
|
index ``2``. Arbitrarily many call policies can be specified. When a ``Nurse``
|
||||||
@ -194,10 +196,36 @@ container:
|
|||||||
Patient != 0) and ``with_custodian_and_ward_postcall`` (if Nurse/Patient ==
|
Patient != 0) and ``with_custodian_and_ward_postcall`` (if Nurse/Patient ==
|
||||||
0) policies from Boost.Python.
|
0) policies from Boost.Python.
|
||||||
|
|
||||||
|
Call guard
|
||||||
|
----------
|
||||||
|
|
||||||
|
The ``call_guard<T>`` policy allows any scope guard type ``T`` to be placed
|
||||||
|
around the function call. For example, this definition:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
m.def("foo", foo, py::call_guard<T>());
|
||||||
|
|
||||||
|
is equivalent to the following pseudocode:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
m.def("foo", [](args...) {
|
||||||
|
T scope_guard;
|
||||||
|
return foo(args...); // forwarded arguments
|
||||||
|
});
|
||||||
|
|
||||||
|
The only requirement is that ``T`` is default-constructible, but otherwise any
|
||||||
|
scope guard will work. This is very useful in combination with `gil_scoped_release`.
|
||||||
|
See :ref:`gil`.
|
||||||
|
|
||||||
|
Multiple guards can also be specified as ``py::call_guard<T1, T2, T3...>``. The
|
||||||
|
constructor order is left to right and destruction happens in reverse.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
The file :file:`tests/test_keep_alive.cpp` contains a complete example
|
The file :file:`tests/test_call_policies.cpp` contains a complete example
|
||||||
that demonstrates using :class:`keep_alive` in more detail.
|
that demonstrates using `keep_alive` and `call_guard` in more detail.
|
||||||
|
|
||||||
.. _python_objects_as_args:
|
.. _python_objects_as_args:
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ T2>, myFunc)``. In this case, the preprocessor assumes that the comma indicates
|
|||||||
the beginning of the next parameter. Use a ``typedef`` to bind the template to
|
the beginning of the next parameter. Use a ``typedef`` to bind the template to
|
||||||
another name and use it in the macro to avoid this problem.
|
another name and use it in the macro to avoid this problem.
|
||||||
|
|
||||||
|
.. _gil:
|
||||||
|
|
||||||
Global Interpreter Lock (GIL)
|
Global Interpreter Lock (GIL)
|
||||||
=============================
|
=============================
|
||||||
@ -68,6 +69,13 @@ could be realized as follows (important changes highlighted):
|
|||||||
return m.ptr();
|
return m.ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
The ``call_go`` wrapper can also be simplified using the `call_guard` policy
|
||||||
|
(see :ref:`call_policies`) which yields the same result:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>());
|
||||||
|
|
||||||
|
|
||||||
Binding sequence data types, iterators, the slicing protocol, etc.
|
Binding sequence data types, iterators, the slicing protocol, etc.
|
||||||
==================================================================
|
==================================================================
|
||||||
|
@ -67,6 +67,44 @@ struct metaclass {
|
|||||||
/// Annotation to mark enums as an arithmetic type
|
/// Annotation to mark enums as an arithmetic type
|
||||||
struct arithmetic { };
|
struct arithmetic { };
|
||||||
|
|
||||||
|
/** \rst
|
||||||
|
A call policy which places one or more guard variables (``Ts...``) around the function call.
|
||||||
|
|
||||||
|
For example, this definition:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
m.def("foo", foo, py::call_guard<T>());
|
||||||
|
|
||||||
|
is equivalent to the following pseudocode:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
m.def("foo", [](args...) {
|
||||||
|
T scope_guard;
|
||||||
|
return foo(args...); // forwarded arguments
|
||||||
|
});
|
||||||
|
\endrst */
|
||||||
|
template <typename... Ts> struct call_guard;
|
||||||
|
|
||||||
|
template <> struct call_guard<> { using type = detail::void_type; };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct call_guard<T> {
|
||||||
|
static_assert(std::is_default_constructible<T>::value,
|
||||||
|
"The guard type must be default constructible");
|
||||||
|
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
struct call_guard<T, Ts...> {
|
||||||
|
struct type {
|
||||||
|
T guard{}; // Compose multiple guard types with left-to-right default-constructor order
|
||||||
|
typename call_guard<Ts...>::type next{};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/// @} annotations
|
/// @} annotations
|
||||||
|
|
||||||
NAMESPACE_BEGIN(detail)
|
NAMESPACE_BEGIN(detail)
|
||||||
@ -374,6 +412,9 @@ struct process_attribute<metaclass> : process_attribute_default<metaclass> {
|
|||||||
template <>
|
template <>
|
||||||
struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};
|
struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
struct process_attribute<call_guard<Ts...>> : process_attribute_default<call_guard<Ts...>> { };
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Process a keep_alive call policy -- invokes keep_alive_impl during the
|
* Process a keep_alive call policy -- invokes keep_alive_impl during the
|
||||||
* pre-call handler if both Nurse, Patient != 0 and use the post-call handler
|
* pre-call handler if both Nurse, Patient != 0 and use the post-call handler
|
||||||
@ -410,6 +451,13 @@ template <typename... Args> struct process_attributes {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using is_call_guard = is_instantiation<call_guard, T>;
|
||||||
|
|
||||||
|
/// Extract the ``type`` from the first `call_guard` in `Extras...` (or `void_type` if none found)
|
||||||
|
template <typename... Extra>
|
||||||
|
using extract_guard_t = typename first_of_t<is_call_guard, call_guard<>, Extra...>::type;
|
||||||
|
|
||||||
/// Check the number of named arguments at compile time
|
/// Check the number of named arguments at compile time
|
||||||
template <typename... Extra,
|
template <typename... Extra,
|
||||||
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
|
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
|
||||||
|
@ -1383,14 +1383,14 @@ public:
|
|||||||
return load_impl_sequence(call, indices{});
|
return load_impl_sequence(call, indices{});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Return, typename Func>
|
template <typename Return, typename Guard, typename Func>
|
||||||
enable_if_t<!std::is_void<Return>::value, Return> call(Func &&f) {
|
enable_if_t<!std::is_void<Return>::value, Return> call(Func &&f) {
|
||||||
return call_impl<Return>(std::forward<Func>(f), indices{});
|
return call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Return, typename Func>
|
template <typename Return, typename Guard, typename Func>
|
||||||
enable_if_t<std::is_void<Return>::value, void_type> call(Func &&f) {
|
enable_if_t<std::is_void<Return>::value, void_type> call(Func &&f) {
|
||||||
call_impl<Return>(std::forward<Func>(f), indices{});
|
call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
|
||||||
return void_type();
|
return void_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1406,8 +1406,8 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Return, typename Func, size_t... Is>
|
template <typename Return, typename Func, size_t... Is, typename Guard>
|
||||||
Return call_impl(Func &&f, index_sequence<Is...>) {
|
Return call_impl(Func &&f, index_sequence<Is...>, Guard &&) {
|
||||||
return std::forward<Func>(f)(cast_op<Args>(std::get<Is>(value))...);
|
return std::forward<Func>(f)(cast_op<Args>(std::get<Is>(value))...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,9 +536,15 @@ using is_template_base_of = decltype(is_template_base_of_impl<Base>::check((remo
|
|||||||
struct is_template_base_of : decltype(is_template_base_of_impl<Base>::check((remove_cv_t<T>*)nullptr)) { };
|
struct is_template_base_of : decltype(is_template_base_of_impl<Base>::check((remove_cv_t<T>*)nullptr)) { };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Check if T is an instantiation of the template `Class`. For example:
|
||||||
|
/// `is_instantiation<shared_ptr, T>` is true if `T == shared_ptr<U>` where U can be anything.
|
||||||
|
template <template<typename...> class Class, typename T>
|
||||||
|
struct is_instantiation : std::false_type { };
|
||||||
|
template <template<typename...> class Class, typename... Us>
|
||||||
|
struct is_instantiation<Class, Class<Us...>> : std::true_type { };
|
||||||
|
|
||||||
/// Check if T is std::shared_ptr<U> where U can be anything
|
/// Check if T is std::shared_ptr<U> where U can be anything
|
||||||
template <typename T> struct is_shared_ptr : std::false_type { };
|
template <typename T> using is_shared_ptr = is_instantiation<std::shared_ptr, T>;
|
||||||
template <typename U> struct is_shared_ptr<std::shared_ptr<U>> : std::true_type { };
|
|
||||||
|
|
||||||
/// Ignore that a variable is unused in compiler warnings
|
/// Ignore that a variable is unused in compiler warnings
|
||||||
inline void ignore_unused(const int *) { }
|
inline void ignore_unused(const int *) { }
|
||||||
|
@ -143,8 +143,11 @@ protected:
|
|||||||
/* Override policy for rvalues -- usually to enforce rvp::move on an rvalue */
|
/* Override policy for rvalues -- usually to enforce rvp::move on an rvalue */
|
||||||
const auto policy = detail::return_value_policy_override<Return>::policy(call.func.policy);
|
const auto policy = detail::return_value_policy_override<Return>::policy(call.func.policy);
|
||||||
|
|
||||||
|
/* Function scope guard -- defaults to the compile-to-nothing `void_type` */
|
||||||
|
using Guard = detail::extract_guard_t<Extra...>;
|
||||||
|
|
||||||
/* Perform the function call */
|
/* Perform the function call */
|
||||||
handle result = cast_out::cast(args_converter.template call<Return>(cap->f),
|
handle result = cast_out::cast(args_converter.template call<Return, Guard>(cap->f),
|
||||||
policy, call.parent);
|
policy, call.parent);
|
||||||
|
|
||||||
/* Invoke call policy post-call hook */
|
/* Invoke call policy post-call hook */
|
||||||
|
@ -28,6 +28,7 @@ endif()
|
|||||||
set(PYBIND11_TEST_FILES
|
set(PYBIND11_TEST_FILES
|
||||||
test_alias_initialization.cpp
|
test_alias_initialization.cpp
|
||||||
test_buffers.cpp
|
test_buffers.cpp
|
||||||
|
test_call_policies.cpp
|
||||||
test_callbacks.cpp
|
test_callbacks.cpp
|
||||||
test_chrono.cpp
|
test_chrono.cpp
|
||||||
test_class_args.cpp
|
test_class_args.cpp
|
||||||
@ -40,7 +41,6 @@ set(PYBIND11_TEST_FILES
|
|||||||
test_exceptions.cpp
|
test_exceptions.cpp
|
||||||
test_inheritance.cpp
|
test_inheritance.cpp
|
||||||
test_issues.cpp
|
test_issues.cpp
|
||||||
test_keep_alive.cpp
|
|
||||||
test_kwargs_and_defaults.cpp
|
test_kwargs_and_defaults.cpp
|
||||||
test_methods_and_attributes.cpp
|
test_methods_and_attributes.cpp
|
||||||
test_modules.cpp
|
test_modules.cpp
|
||||||
|
91
tests/test_call_policies.cpp
Normal file
91
tests/test_call_policies.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
tests/test_call_policies.cpp -- keep_alive and call_guard
|
||||||
|
|
||||||
|
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
|
||||||
|
|
||||||
|
All rights reserved. Use of this source code is governed by a
|
||||||
|
BSD-style license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pybind11_tests.h"
|
||||||
|
|
||||||
|
class Child {
|
||||||
|
public:
|
||||||
|
Child() { py::print("Allocating child."); }
|
||||||
|
~Child() { py::print("Releasing child."); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
public:
|
||||||
|
Parent() { py::print("Allocating parent."); }
|
||||||
|
~Parent() { py::print("Releasing parent."); }
|
||||||
|
void addChild(Child *) { }
|
||||||
|
Child *returnChild() { return new Child(); }
|
||||||
|
Child *returnNullChild() { return nullptr; }
|
||||||
|
};
|
||||||
|
|
||||||
|
test_initializer keep_alive([](py::module &m) {
|
||||||
|
py::class_<Parent>(m, "Parent")
|
||||||
|
.def(py::init<>())
|
||||||
|
.def("addChild", &Parent::addChild)
|
||||||
|
.def("addChildKeepAlive", &Parent::addChild, py::keep_alive<1, 2>())
|
||||||
|
.def("returnChild", &Parent::returnChild)
|
||||||
|
.def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>())
|
||||||
|
.def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>())
|
||||||
|
.def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>());
|
||||||
|
|
||||||
|
py::class_<Child>(m, "Child")
|
||||||
|
.def(py::init<>());
|
||||||
|
});
|
||||||
|
|
||||||
|
struct CustomGuard {
|
||||||
|
static bool enabled;
|
||||||
|
|
||||||
|
CustomGuard() { enabled = true; }
|
||||||
|
~CustomGuard() { enabled = false; }
|
||||||
|
|
||||||
|
static const char *report_status() { return enabled ? "guarded" : "unguarded"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
bool CustomGuard::enabled = false;
|
||||||
|
|
||||||
|
struct DependentGuard {
|
||||||
|
static bool enabled;
|
||||||
|
|
||||||
|
DependentGuard() { enabled = CustomGuard::enabled; }
|
||||||
|
~DependentGuard() { enabled = false; }
|
||||||
|
|
||||||
|
static const char *report_status() { return enabled ? "guarded" : "unguarded"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
bool DependentGuard::enabled = false;
|
||||||
|
|
||||||
|
test_initializer call_guard([](py::module &pm) {
|
||||||
|
auto m = pm.def_submodule("call_policies");
|
||||||
|
|
||||||
|
m.def("unguarded_call", &CustomGuard::report_status);
|
||||||
|
m.def("guarded_call", &CustomGuard::report_status, py::call_guard<CustomGuard>());
|
||||||
|
|
||||||
|
m.def("multiple_guards_correct_order", []() {
|
||||||
|
return CustomGuard::report_status() + std::string(" & ") + DependentGuard::report_status();
|
||||||
|
}, py::call_guard<CustomGuard, DependentGuard>());
|
||||||
|
|
||||||
|
m.def("multiple_guards_wrong_order", []() {
|
||||||
|
return DependentGuard::report_status() + std::string(" & ") + CustomGuard::report_status();
|
||||||
|
}, py::call_guard<DependentGuard, CustomGuard>());
|
||||||
|
|
||||||
|
#if defined(WITH_THREAD) && !defined(PYPY_VERSION)
|
||||||
|
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy as well,
|
||||||
|
// but it's unclear how to test it without `PyGILState_GetThisThreadState`.
|
||||||
|
auto report_gil_status = []() {
|
||||||
|
auto is_gil_held = false;
|
||||||
|
if (auto tstate = py::detail::get_thread_state_unchecked())
|
||||||
|
is_gil_held = (tstate == PyGILState_GetThisThreadState());
|
||||||
|
|
||||||
|
return is_gil_held ? "GIL held" : "GIL released";
|
||||||
|
};
|
||||||
|
|
||||||
|
m.def("with_gil", report_gil_status);
|
||||||
|
m.def("without_gil", report_gil_status, py::call_guard<py::gil_scoped_release>());
|
||||||
|
#endif
|
||||||
|
});
|
@ -95,3 +95,17 @@ def test_return_none(capture):
|
|||||||
del p
|
del p
|
||||||
pytest.gc_collect()
|
pytest.gc_collect()
|
||||||
assert capture == "Releasing parent."
|
assert capture == "Releasing parent."
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_guard():
|
||||||
|
from pybind11_tests import call_policies
|
||||||
|
|
||||||
|
assert call_policies.unguarded_call() == "unguarded"
|
||||||
|
assert call_policies.guarded_call() == "guarded"
|
||||||
|
|
||||||
|
assert call_policies.multiple_guards_correct_order() == "guarded & guarded"
|
||||||
|
assert call_policies.multiple_guards_wrong_order() == "unguarded & guarded"
|
||||||
|
|
||||||
|
if hasattr(call_policies, "with_gil"):
|
||||||
|
assert call_policies.with_gil() == "GIL held"
|
||||||
|
assert call_policies.without_gil() == "GIL released"
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
tests/test_keep_alive.cpp -- keep_alive modifier (pybind11's version
|
|
||||||
of Boost.Python's with_custodian_and_ward / with_custodian_and_ward_postcall)
|
|
||||||
|
|
||||||
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
|
|
||||||
|
|
||||||
All rights reserved. Use of this source code is governed by a
|
|
||||||
BSD-style license that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "pybind11_tests.h"
|
|
||||||
|
|
||||||
class Child {
|
|
||||||
public:
|
|
||||||
Child() { py::print("Allocating child."); }
|
|
||||||
~Child() { py::print("Releasing child."); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class Parent {
|
|
||||||
public:
|
|
||||||
Parent() { py::print("Allocating parent."); }
|
|
||||||
~Parent() { py::print("Releasing parent."); }
|
|
||||||
void addChild(Child *) { }
|
|
||||||
Child *returnChild() { return new Child(); }
|
|
||||||
Child *returnNullChild() { return nullptr; }
|
|
||||||
};
|
|
||||||
|
|
||||||
test_initializer keep_alive([](py::module &m) {
|
|
||||||
py::class_<Parent>(m, "Parent")
|
|
||||||
.def(py::init<>())
|
|
||||||
.def("addChild", &Parent::addChild)
|
|
||||||
.def("addChildKeepAlive", &Parent::addChild, py::keep_alive<1, 2>())
|
|
||||||
.def("returnChild", &Parent::returnChild)
|
|
||||||
.def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>())
|
|
||||||
.def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>())
|
|
||||||
.def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>());
|
|
||||||
|
|
||||||
py::class_<Child>(m, "Child")
|
|
||||||
.def(py::init<>());
|
|
||||||
});
|
|
Loading…
Reference in New Issue
Block a user