mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 14:45:12 +00:00
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:
parent
7653a115bd
commit
a01b6b805c
@ -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>());
|
||||||
};
|
};
|
||||||
|
@ -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 {
|
||||||
|
@ -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():
|
|||||||
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,
|
||||||
|
Loading…
Reference in New Issue
Block a user