mirror of
https://github.com/pybind/pybind11.git
synced 2025-02-16 13:47:53 +00:00
Adds automatic casting on assignment of non-pyobject types (#551)
This adds automatic casting when assigning to python types like dict, list, and attributes. Instead of: dict["key"] = py::cast(val); m.attr("foo") = py::cast(true); list.append(py::cast(42)); you can now simply write: dict["key"] = val; m.attr("foo") = true; list.append(42); Casts needing extra parameters (e.g. for a non-default rvp) still require the py::cast() call. set::add() is also supported. All usage is channeled through a SFINAE implementation which either just returns or casts. Combined non-converting handle and autocasting template methods via a helper method that either just returns (handle) or casts (C++ type).
This commit is contained in:
parent
7c9ef7b553
commit
3f1ff3f4d1
@ -254,16 +254,18 @@ The shorthand notation is also available for default arguments:
|
|||||||
Exporting variables
|
Exporting variables
|
||||||
===================
|
===================
|
||||||
|
|
||||||
To expose a value from C++, use the ``attr`` function to register it in a module
|
To expose a value from C++, use the ``attr`` function to register it in a
|
||||||
as shown below. Built-in types and general objects (more on that later) can be
|
module as shown below. Built-in types and general objects (more on that later)
|
||||||
|
are automatically converted when assigned as attributes, and can be explicitly
|
||||||
converted using the function ``py::cast``.
|
converted using the function ``py::cast``.
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
PYBIND11_PLUGIN(example) {
|
PYBIND11_PLUGIN(example) {
|
||||||
py::module m("example", "pybind11 example plugin");
|
py::module m("example", "pybind11 example plugin");
|
||||||
m.attr("the_answer") = py::cast(42);
|
m.attr("the_answer") = 42;
|
||||||
m.attr("what") = py::cast("World");
|
py::object world = py::cast("World");
|
||||||
|
m.attr("what") = world;
|
||||||
return m.ptr();
|
return m.ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1120,6 +1120,10 @@ template <> inline void object::cast() && { return; }
|
|||||||
|
|
||||||
NAMESPACE_BEGIN(detail)
|
NAMESPACE_BEGIN(detail)
|
||||||
|
|
||||||
|
// Declared in pytypes.h:
|
||||||
|
template <typename T, enable_if_t<!is_pyobject<T>::value, int>>
|
||||||
|
object object_or_cast(T &&o) { return pybind11::cast(std::forward<T>(o)); }
|
||||||
|
|
||||||
struct overload_unused {}; // Placeholder type for the unneeded (and dead code) static variable in the OVERLOAD_INT macro
|
struct overload_unused {}; // Placeholder type for the unneeded (and dead code) static variable in the OVERLOAD_INT macro
|
||||||
template <typename ret_type> using overload_caster_t = conditional_t<
|
template <typename ret_type> using overload_caster_t = conditional_t<
|
||||||
cast_is_temporary_value_reference<ret_type>::value, make_caster<ret_type>, overload_unused>;
|
cast_is_temporary_value_reference<ret_type>::value, make_caster<ret_type>, overload_unused>;
|
||||||
|
@ -43,7 +43,7 @@ using tuple_accessor = accessor<accessor_policies::tuple_item>;
|
|||||||
|
|
||||||
/// Tag and check to identify a class which implements the Python object API
|
/// Tag and check to identify a class which implements the Python object API
|
||||||
class pyobject_tag { };
|
class pyobject_tag { };
|
||||||
template <typename T> using is_pyobject = std::is_base_of<pyobject_tag, T>;
|
template <typename T> using is_pyobject = std::is_base_of<pyobject_tag, typename std::remove_reference<T>::type>;
|
||||||
|
|
||||||
/// Mixin which adds common functions to handle, object and various accessors.
|
/// Mixin which adds common functions to handle, object and various accessors.
|
||||||
/// The only requirement for `Derived` is to implement `PyObject *Derived::ptr() const`.
|
/// The only requirement for `Derived` is to implement `PyObject *Derived::ptr() const`.
|
||||||
@ -81,7 +81,7 @@ NAMESPACE_END(detail)
|
|||||||
class handle : public detail::object_api<handle> {
|
class handle : public detail::object_api<handle> {
|
||||||
public:
|
public:
|
||||||
handle() = default;
|
handle() = default;
|
||||||
handle(PyObject *ptr) : m_ptr(ptr) { }
|
handle(PyObject *ptr) : m_ptr(ptr) { } // Allow implicit conversion from PyObject*
|
||||||
|
|
||||||
PyObject *ptr() const { return m_ptr; }
|
PyObject *ptr() const { return m_ptr; }
|
||||||
PyObject *&ptr() { return m_ptr; }
|
PyObject *&ptr() { return m_ptr; }
|
||||||
@ -224,6 +224,18 @@ inline handle get_function(handle value) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper aliases/functions to support implicit casting of values given to python accessors/methods.
|
||||||
|
// When given a pyobject, this simply returns the pyobject as-is; for other C++ type, the value goes
|
||||||
|
// through pybind11::cast(obj) to convert it to an `object`.
|
||||||
|
template <typename T, enable_if_t<is_pyobject<T>::value, int> = 0>
|
||||||
|
auto object_or_cast(T &&o) -> decltype(std::forward<T>(o)) { return std::forward<T>(o); }
|
||||||
|
// The following casting version is implemented in cast.h:
|
||||||
|
template <typename T, enable_if_t<!is_pyobject<T>::value, int> = 0>
|
||||||
|
object object_or_cast(T &&o);
|
||||||
|
// Match a PyObject*, which we want to convert directly to handle via its converting constructor
|
||||||
|
inline handle object_or_cast(PyObject *ptr) { return ptr; }
|
||||||
|
|
||||||
|
|
||||||
template <typename Policy>
|
template <typename Policy>
|
||||||
class accessor : public object_api<accessor<Policy>> {
|
class accessor : public object_api<accessor<Policy>> {
|
||||||
using key_type = typename Policy::key_type;
|
using key_type = typename Policy::key_type;
|
||||||
@ -231,12 +243,17 @@ class accessor : public object_api<accessor<Policy>> {
|
|||||||
public:
|
public:
|
||||||
accessor(handle obj, key_type key) : obj(obj), key(std::move(key)) { }
|
accessor(handle obj, key_type key) : obj(obj), key(std::move(key)) { }
|
||||||
|
|
||||||
|
// accessor overload required to override default assignment operator (templates are not allowed
|
||||||
|
// to replace default compiler-generated assignments).
|
||||||
void operator=(const accessor &a) && { std::move(*this).operator=(handle(a)); }
|
void operator=(const accessor &a) && { std::move(*this).operator=(handle(a)); }
|
||||||
void operator=(const accessor &a) & { operator=(handle(a)); }
|
void operator=(const accessor &a) & { operator=(handle(a)); }
|
||||||
void operator=(const object &o) && { std::move(*this).operator=(handle(o)); }
|
|
||||||
void operator=(const object &o) & { operator=(handle(o)); }
|
template <typename T> void operator=(T &&value) && {
|
||||||
void operator=(handle value) && { Policy::set(obj, key, value); }
|
Policy::set(obj, key, object_or_cast(std::forward<T>(value)));
|
||||||
void operator=(handle value) & { get_cache() = reinterpret_borrow<object>(value); }
|
}
|
||||||
|
template <typename T> void operator=(T &&value) & {
|
||||||
|
get_cache() = reinterpret_borrow<object>(object_or_cast(std::forward<T>(value)));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T = Policy>
|
template <typename T = Policy>
|
||||||
PYBIND11_DEPRECATED("Use of obj.attr(...) as bool is deprecated in favor of pybind11::hasattr(obj, ...)")
|
PYBIND11_DEPRECATED("Use of obj.attr(...) as bool is deprecated in favor of pybind11::hasattr(obj, ...)")
|
||||||
@ -773,7 +790,9 @@ public:
|
|||||||
}
|
}
|
||||||
size_t size() const { return (size_t) PyList_Size(m_ptr); }
|
size_t size() const { return (size_t) PyList_Size(m_ptr); }
|
||||||
detail::list_accessor operator[](size_t index) const { return {*this, index}; }
|
detail::list_accessor operator[](size_t index) const { return {*this, index}; }
|
||||||
void append(handle h) const { PyList_Append(m_ptr, h.ptr()); }
|
template <typename T> void append(T &&val) const {
|
||||||
|
PyList_Append(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class args : public tuple { PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check) };
|
class args : public tuple { PYBIND11_OBJECT_DEFAULT(args, tuple, PyTuple_Check) };
|
||||||
@ -786,7 +805,9 @@ public:
|
|||||||
if (!m_ptr) pybind11_fail("Could not allocate set object!");
|
if (!m_ptr) pybind11_fail("Could not allocate set object!");
|
||||||
}
|
}
|
||||||
size_t size() const { return (size_t) PySet_Size(m_ptr); }
|
size_t size() const { return (size_t) PySet_Size(m_ptr); }
|
||||||
bool add(const object &object) const { return PySet_Add(m_ptr, object.ptr()) == 0; }
|
template <typename T> bool add(T &&val) const {
|
||||||
|
return PySet_Add(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) == 0;
|
||||||
|
}
|
||||||
void clear() const { PySet_Clear(m_ptr); }
|
void clear() const { PySet_Clear(m_ptr); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ PYBIND11_PLUGIN(pybind11_tests) {
|
|||||||
for (const auto &initializer : initializers())
|
for (const auto &initializer : initializers())
|
||||||
initializer(m);
|
initializer(m);
|
||||||
|
|
||||||
if (!py::hasattr(m, "have_eigen")) m.attr("have_eigen") = py::cast(false);
|
if (!py::hasattr(m, "have_eigen")) m.attr("have_eigen") = false;
|
||||||
|
|
||||||
return m.ptr();
|
return m.ptr();
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ test_initializer eigen([](py::module &m) {
|
|||||||
typedef Eigen::SparseMatrix<float, Eigen::RowMajor> SparseMatrixR;
|
typedef Eigen::SparseMatrix<float, Eigen::RowMajor> SparseMatrixR;
|
||||||
typedef Eigen::SparseMatrix<float> SparseMatrixC;
|
typedef Eigen::SparseMatrix<float> SparseMatrixC;
|
||||||
|
|
||||||
m.attr("have_eigen") = py::cast(true);
|
m.attr("have_eigen") = true;
|
||||||
|
|
||||||
// Non-symmetric matrix with zero elements
|
// Non-symmetric matrix with zero elements
|
||||||
Eigen::MatrixXf mat(5, 6);
|
Eigen::MatrixXf mat(5, 6);
|
||||||
|
@ -88,7 +88,7 @@ void throws_logic_error() {
|
|||||||
|
|
||||||
struct PythonCallInDestructor {
|
struct PythonCallInDestructor {
|
||||||
PythonCallInDestructor(const py::dict &d) : d(d) {}
|
PythonCallInDestructor(const py::dict &d) : d(d) {}
|
||||||
~PythonCallInDestructor() { d["good"] = py::cast(true); }
|
~PythonCallInDestructor() { d["good"] = true; }
|
||||||
|
|
||||||
py::dict d;
|
py::dict d;
|
||||||
};
|
};
|
||||||
|
@ -381,6 +381,11 @@ void init_issues(py::module &m) {
|
|||||||
.def_static("make", &MyDerived::make)
|
.def_static("make", &MyDerived::make)
|
||||||
.def_static("make2", &MyDerived::make);
|
.def_static("make2", &MyDerived::make);
|
||||||
|
|
||||||
|
py::dict d;
|
||||||
|
std::string bar = "bar";
|
||||||
|
d["str"] = bar;
|
||||||
|
d["num"] = 3.7;
|
||||||
|
|
||||||
/// Issue #528: templated constructor
|
/// Issue #528: templated constructor
|
||||||
m2.def("tpl_constr_vector", [](std::vector<TplConstrClass> &) {});
|
m2.def("tpl_constr_vector", [](std::vector<TplConstrClass> &) {});
|
||||||
m2.def("tpl_constr_map", [](std::unordered_map<TplConstrClass, TplConstrClass> &) {});
|
m2.def("tpl_constr_map", [](std::unordered_map<TplConstrClass, TplConstrClass> &) {});
|
||||||
|
@ -37,7 +37,8 @@ public:
|
|||||||
py::set get_set() {
|
py::set get_set() {
|
||||||
py::set set;
|
py::set set;
|
||||||
set.add(py::str("key1"));
|
set.add(py::str("key1"));
|
||||||
set.add(py::str("key2"));
|
set.add("key2");
|
||||||
|
set.add(std::string("key3"));
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ public:
|
|||||||
/* Create, manipulate, and return a Python list */
|
/* Create, manipulate, and return a Python list */
|
||||||
py::list get_list() {
|
py::list get_list() {
|
||||||
py::list list;
|
py::list list;
|
||||||
list.append(py::str("value"));
|
list.append("value");
|
||||||
py::print("Entry at position 0:", list[0]);
|
py::print("Entry at position 0:", list[0]);
|
||||||
list[0] = py::str("overwritten");
|
list[0] = py::str("overwritten");
|
||||||
return list;
|
return list;
|
||||||
@ -269,7 +270,7 @@ test_initializer python_types([](py::module &m) {
|
|||||||
d["missing_attr_chain"] = "raised"_s;
|
d["missing_attr_chain"] = "raised"_s;
|
||||||
}
|
}
|
||||||
|
|
||||||
d["is_none"] = py::cast(o.attr("basic_attr").is_none());
|
d["is_none"] = o.attr("basic_attr").is_none();
|
||||||
|
|
||||||
d["operator()"] = o.attr("func")(1);
|
d["operator()"] = o.attr("func")(1);
|
||||||
d["operator*"] = o.attr("func")(*o.attr("begin_end"));
|
d["operator*"] = o.attr("func")(*o.attr("begin_end"));
|
||||||
@ -279,13 +280,13 @@ test_initializer python_types([](py::module &m) {
|
|||||||
|
|
||||||
m.def("test_tuple_accessor", [](py::tuple existing_t) {
|
m.def("test_tuple_accessor", [](py::tuple existing_t) {
|
||||||
try {
|
try {
|
||||||
existing_t[0] = py::cast(1);
|
existing_t[0] = 1;
|
||||||
} catch (const py::error_already_set &) {
|
} catch (const py::error_already_set &) {
|
||||||
// --> Python system error
|
// --> Python system error
|
||||||
// Only new tuples (refcount == 1) are mutable
|
// Only new tuples (refcount == 1) are mutable
|
||||||
auto new_t = py::tuple(3);
|
auto new_t = py::tuple(3);
|
||||||
for (size_t i = 0; i < new_t.size(); ++i) {
|
for (size_t i = 0; i < new_t.size(); ++i) {
|
||||||
new_t[i] = py::cast(i);
|
new_t[i] = i;
|
||||||
}
|
}
|
||||||
return new_t;
|
return new_t;
|
||||||
}
|
}
|
||||||
@ -294,15 +295,15 @@ test_initializer python_types([](py::module &m) {
|
|||||||
|
|
||||||
m.def("test_accessor_assignment", []() {
|
m.def("test_accessor_assignment", []() {
|
||||||
auto l = py::list(1);
|
auto l = py::list(1);
|
||||||
l[0] = py::cast(0);
|
l[0] = 0;
|
||||||
|
|
||||||
auto d = py::dict();
|
auto d = py::dict();
|
||||||
d["get"] = l[0];
|
d["get"] = l[0];
|
||||||
auto var = l[0];
|
auto var = l[0];
|
||||||
d["deferred_get"] = var;
|
d["deferred_get"] = var;
|
||||||
l[0] = py::cast(1);
|
l[0] = 1;
|
||||||
d["set"] = l[0];
|
d["set"] = l[0];
|
||||||
var = py::cast(99); // this assignment should not overwrite l[0]
|
var = 99; // this assignment should not overwrite l[0]
|
||||||
d["deferred_set"] = l[0];
|
d["deferred_set"] = l[0];
|
||||||
d["var"] = var;
|
d["var"] = var;
|
||||||
|
|
||||||
@ -338,8 +339,8 @@ test_initializer python_types([](py::module &m) {
|
|||||||
}, py::arg_v("x", std::experimental::nullopt, "None"));
|
}, py::arg_v("x", std::experimental::nullopt, "None"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m.attr("has_optional") = py::cast(has_optional);
|
m.attr("has_optional") = has_optional;
|
||||||
m.attr("has_exp_optional") = py::cast(has_exp_optional);
|
m.attr("has_exp_optional") = has_exp_optional;
|
||||||
|
|
||||||
m.def("test_default_constructors", []() {
|
m.def("test_default_constructors", []() {
|
||||||
return py::dict(
|
return py::dict(
|
||||||
@ -389,4 +390,41 @@ test_initializer python_types([](py::module &m) {
|
|||||||
py::class_<MoveOutContainer>(m, "MoveOutContainer")
|
py::class_<MoveOutContainer>(m, "MoveOutContainer")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def_property_readonly("move_list", &MoveOutContainer::move_list);
|
.def_property_readonly("move_list", &MoveOutContainer::move_list);
|
||||||
|
|
||||||
|
m.def("get_implicit_casting", []() {
|
||||||
|
py::dict d;
|
||||||
|
d["char*_i1"] = "abc";
|
||||||
|
const char *c2 = "abc";
|
||||||
|
d["char*_i2"] = c2;
|
||||||
|
d["char*_e"] = py::cast(c2);
|
||||||
|
d["char*_p"] = py::str(c2);
|
||||||
|
|
||||||
|
d["int_i1"] = 42;
|
||||||
|
int i = 42;
|
||||||
|
d["int_i2"] = i;
|
||||||
|
i++;
|
||||||
|
d["int_e"] = py::cast(i);
|
||||||
|
i++;
|
||||||
|
d["int_p"] = py::int_(i);
|
||||||
|
|
||||||
|
d["str_i1"] = std::string("str");
|
||||||
|
std::string s2("str1");
|
||||||
|
d["str_i2"] = s2;
|
||||||
|
s2[3] = '2';
|
||||||
|
d["str_e"] = py::cast(s2);
|
||||||
|
s2[3] = '3';
|
||||||
|
d["str_p"] = py::str(s2);
|
||||||
|
|
||||||
|
py::list l(2);
|
||||||
|
l[0] = 3;
|
||||||
|
l[1] = py::cast(6);
|
||||||
|
l.append(9);
|
||||||
|
l.append(py::cast(12));
|
||||||
|
l.append(py::int_(15));
|
||||||
|
|
||||||
|
return py::dict(
|
||||||
|
"d"_a=d,
|
||||||
|
"l"_a=l
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -38,12 +38,13 @@ def test_instance(capture):
|
|||||||
"""
|
"""
|
||||||
with capture:
|
with capture:
|
||||||
set_result = instance.get_set()
|
set_result = instance.get_set()
|
||||||
set_result.add('key3')
|
set_result.add('key4')
|
||||||
instance.print_set(set_result)
|
instance.print_set(set_result)
|
||||||
assert capture.unordered == """
|
assert capture.unordered == """
|
||||||
key: key1
|
key: key1
|
||||||
key: key2
|
key: key2
|
||||||
key: key3
|
key: key3
|
||||||
|
key: key4
|
||||||
"""
|
"""
|
||||||
with capture:
|
with capture:
|
||||||
set_result = instance.get_set2()
|
set_result = instance.get_set2()
|
||||||
@ -386,3 +387,16 @@ def test_move_out_container():
|
|||||||
c = MoveOutContainer()
|
c = MoveOutContainer()
|
||||||
moved_out_list = c.move_list
|
moved_out_list = c.move_list
|
||||||
assert [x.value for x in moved_out_list] == [0, 1, 2]
|
assert [x.value for x in moved_out_list] == [0, 1, 2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_implicit_casting():
|
||||||
|
"""Tests implicit casting when assigning or appending to dicts and lists."""
|
||||||
|
from pybind11_tests import get_implicit_casting
|
||||||
|
|
||||||
|
z = get_implicit_casting()
|
||||||
|
assert z['d'] == {
|
||||||
|
'char*_i1': 'abc', 'char*_i2': 'abc', 'char*_e': 'abc', 'char*_p': 'abc',
|
||||||
|
'str_i1': 'str', 'str_i2': 'str1', 'str_e': 'str2', 'str_p': 'str3',
|
||||||
|
'int_i1': 42, 'int_i2': 42, 'int_e': 43, 'int_p': 44
|
||||||
|
}
|
||||||
|
assert z['l'] == [3, 6, 9, 12, 15]
|
||||||
|
Loading…
Reference in New Issue
Block a user