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

View File

@ -355,6 +355,7 @@ inline handle get_function(handle value) {
#if PY_MAJOR_VERSION >= 3 #if PY_MAJOR_VERSION >= 3
if (PyInstanceMethod_Check(value.ptr())) if (PyInstanceMethod_Check(value.ptr()))
value = PyInstanceMethod_GET_FUNCTION(value.ptr()); value = PyInstanceMethod_GET_FUNCTION(value.ptr());
else
#endif #endif
if (PyMethod_Check(value.ptr())) if (PyMethod_Check(value.ptr()))
value = PyMethod_GET_FUNCTION(value.ptr()); value = PyMethod_GET_FUNCTION(value.ptr());
@ -1133,10 +1134,13 @@ public:
class function : public object { class function : public object {
public: public:
PYBIND11_OBJECT_DEFAULT(function, object, PyCallable_Check) PYBIND11_OBJECT_DEFAULT(function, object, PyCallable_Check)
bool is_cpp_function() const { handle cpp_function() const {
handle fun = detail::get_function(m_ptr); 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 { 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 f(x); // lvalue reference shouldn't move out object
return x.valid; // must still return `true` 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 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(): def test_keyword_args_and_generalized_unpacking():
from pybind11_tests import (test_tuple_unpacking, test_dict_unpacking, test_keyword_args, from pybind11_tests import (test_tuple_unpacking, test_dict_unpacking, test_keyword_args,
test_unpacking_and_keywords1, test_unpacking_and_keywords2, test_unpacking_and_keywords1, test_unpacking_and_keywords2,