functional: support bound methods

If a bound std::function is invoked with a bound method, the implicit
bound self is lost because we use `detail::get_function` to unbox the
function.  This commit amends the code to use py::function and only
unboxes in the special is-really-a-c-function case.  This makes bound
methods stay bound rather than unbinding them by forcing extraction of
the c function.
This commit is contained in:
Jason Rhinelander 2017-04-24 12:29:42 -04:00
parent 7653a115bd
commit a01b6b805c
4 changed files with 35 additions and 11 deletions

View File

@ -22,14 +22,15 @@ struct type_caster<std::function<Return(Args...)>> {
using function_type = Return (*) (Args...);
public:
bool load(handle src_, bool) {
if (src_.is_none())
bool load(handle src, bool) {
if (src.is_none())
return true;
src_ = detail::get_function(src_);
if (!src_ || !PyCallable_Check(src_.ptr()))
if (!isinstance<function>(src))
return false;
auto func = reinterpret_borrow<function>(src);
/*
When passing a C++ function as an argument to another C++
function via Python, every function call would normally involve
@ -38,8 +39,8 @@ public:
stateless (i.e. function pointer or lambda function without
captured variables), in which case the roundtrip can be avoided.
*/
if (PyCFunction_Check(src_.ptr())) {
auto c = reinterpret_borrow<capsule>(PyCFunction_GET_SELF(src_.ptr()));
if (auto cfunc = func.cpp_function()) {
auto c = reinterpret_borrow<capsule>(PyCFunction_GET_SELF(cfunc.ptr()));
auto rec = (function_record *) c;
if (rec && rec->is_stateless && rec->data[1] == &typeid(function_type)) {
@ -49,10 +50,9 @@ public:
}
}
auto src = reinterpret_borrow<object>(src_);
value = [src](Args... args) -> Return {
value = [func](Args... args) -> Return {
gil_scoped_acquire acq;
object retval(src(std::forward<Args>(args)...));
object retval(func(std::forward<Args>(args)...));
/* Visual studio 2015 parser issue: need parentheses around this expression */
return (retval.template cast<Return>());
};

View File

@ -355,6 +355,7 @@ inline handle get_function(handle value) {
#if PY_MAJOR_VERSION >= 3
if (PyInstanceMethod_Check(value.ptr()))
value = PyInstanceMethod_GET_FUNCTION(value.ptr());
else
#endif
if (PyMethod_Check(value.ptr()))
value = PyMethod_GET_FUNCTION(value.ptr());
@ -1133,10 +1134,13 @@ public:
class function : public object {
public:
PYBIND11_OBJECT_DEFAULT(function, object, PyCallable_Check)
bool is_cpp_function() const {
handle cpp_function() const {
handle fun = detail::get_function(m_ptr);
return fun && PyCFunction_Check(fun.ptr());
if (fun && PyCFunction_Check(fun.ptr()))
return fun;
return handle();
}
bool is_cpp_function() const { return (bool) cpp_function(); }
};
class buffer : public object {

View File

@ -179,4 +179,9 @@ test_initializer callbacks([](py::module &m) {
f(x); // lvalue reference shouldn't move out object
return x.valid; // must still return `true`
});
struct CppBoundMethodTest {};
py::class_<CppBoundMethodTest>(m, "CppBoundMethodTest")
.def(py::init<>())
.def("triple", [](CppBoundMethodTest &, int val) { return 3 * val; });
});

View File

@ -27,6 +27,21 @@ def test_callbacks():
assert f(number=43) == 44
def test_bound_method_callback():
from pybind11_tests import test_callback3, CppBoundMethodTest
# Bound Python method:
class MyClass:
def double(self, val):
return 2 * val
z = MyClass()
assert test_callback3(z.double) == "func(43) = 86"
z = CppBoundMethodTest()
assert test_callback3(z.triple) == "func(43) = 129"
def test_keyword_args_and_generalized_unpacking():
from pybind11_tests import (test_tuple_unpacking, test_dict_unpacking, test_keyword_args,
test_unpacking_and_keywords1, test_unpacking_and_keywords2,