Commit a01b6b80 by Jason Rhinelander

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.
parent 7653a115
...@@ -22,14 +22,15 @@ struct type_caster<std::function<Return(Args...)>> { ...@@ -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: ...@@ -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: ...@@ -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>());
}; };
......
...@@ -355,6 +355,7 @@ inline handle get_function(handle value) { ...@@ -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: ...@@ -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 {
......
...@@ -179,4 +179,9 @@ test_initializer callbacks([](py::module &m) { ...@@ -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; });
}); });
...@@ -27,6 +27,21 @@ def test_callbacks(): ...@@ -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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment