mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +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
|
||||
===================
|
||||
|
||||
To expose a value from C++, use the ``attr`` function to register it in a module
|
||||
as shown below. Built-in types and general objects (more on that later) can be
|
||||
To expose a value from C++, use the ``attr`` function to register it in a
|
||||
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``.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
PYBIND11_PLUGIN(example) {
|
||||
py::module m("example", "pybind11 example plugin");
|
||||
m.attr("the_answer") = py::cast(42);
|
||||
m.attr("what") = py::cast("World");
|
||||
m.attr("the_answer") = 42;
|
||||
py::object world = py::cast("World");
|
||||
m.attr("what") = world;
|
||||
return m.ptr();
|
||||
}
|
||||
|
||||
|
@ -1120,6 +1120,10 @@ template <> inline void object::cast() && { return; }
|
||||
|
||||
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
|
||||
template <typename ret_type> using overload_caster_t = conditional_t<
|
||||
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
|
||||
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.
|
||||
/// 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> {
|
||||
public:
|
||||
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() { return m_ptr; }
|
||||
@ -224,6 +224,18 @@ inline handle get_function(handle 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>
|
||||
class accessor : public object_api<accessor<Policy>> {
|
||||
using key_type = typename Policy::key_type;
|
||||
@ -231,12 +243,17 @@ class accessor : public object_api<accessor<Policy>> {
|
||||
public:
|
||||
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) & { operator=(handle(a)); }
|
||||
void operator=(const object &o) && { std::move(*this).operator=(handle(o)); }
|
||||
void operator=(const object &o) & { operator=(handle(o)); }
|
||||
void operator=(handle value) && { Policy::set(obj, key, value); }
|
||||
void operator=(handle value) & { get_cache() = reinterpret_borrow<object>(value); }
|
||||
|
||||
template <typename T> void operator=(T &&value) && {
|
||||
Policy::set(obj, key, object_or_cast(std::forward<T>(value)));
|
||||
}
|
||||
template <typename T> void operator=(T &&value) & {
|
||||
get_cache() = reinterpret_borrow<object>(object_or_cast(std::forward<T>(value)));
|
||||
}
|
||||
|
||||
template <typename T = Policy>
|
||||
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); }
|
||||
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) };
|
||||
@ -786,7 +805,9 @@ public:
|
||||
if (!m_ptr) pybind11_fail("Could not allocate set object!");
|
||||
}
|
||||
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); }
|
||||
};
|
||||
|
||||
|
@ -39,7 +39,7 @@ PYBIND11_PLUGIN(pybind11_tests) {
|
||||
for (const auto &initializer : initializers())
|
||||
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();
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ test_initializer eigen([](py::module &m) {
|
||||
typedef Eigen::SparseMatrix<float, Eigen::RowMajor> SparseMatrixR;
|
||||
typedef Eigen::SparseMatrix<float> SparseMatrixC;
|
||||
|
||||
m.attr("have_eigen") = py::cast(true);
|
||||
m.attr("have_eigen") = true;
|
||||
|
||||
// Non-symmetric matrix with zero elements
|
||||
Eigen::MatrixXf mat(5, 6);
|
||||
|
@ -88,7 +88,7 @@ void throws_logic_error() {
|
||||
|
||||
struct PythonCallInDestructor {
|
||||
PythonCallInDestructor(const py::dict &d) : d(d) {}
|
||||
~PythonCallInDestructor() { d["good"] = py::cast(true); }
|
||||
~PythonCallInDestructor() { d["good"] = true; }
|
||||
|
||||
py::dict d;
|
||||
};
|
||||
|
@ -381,6 +381,11 @@ void init_issues(py::module &m) {
|
||||
.def_static("make", &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
|
||||
m2.def("tpl_constr_vector", [](std::vector<TplConstrClass> &) {});
|
||||
m2.def("tpl_constr_map", [](std::unordered_map<TplConstrClass, TplConstrClass> &) {});
|
||||
|
@ -37,7 +37,8 @@ public:
|
||||
py::set get_set() {
|
||||
py::set set;
|
||||
set.add(py::str("key1"));
|
||||
set.add(py::str("key2"));
|
||||
set.add("key2");
|
||||
set.add(std::string("key3"));
|
||||
return set;
|
||||
}
|
||||
|
||||
@ -59,7 +60,7 @@ public:
|
||||
/* Create, manipulate, and return a Python list */
|
||||
py::list get_list() {
|
||||
py::list list;
|
||||
list.append(py::str("value"));
|
||||
list.append("value");
|
||||
py::print("Entry at position 0:", list[0]);
|
||||
list[0] = py::str("overwritten");
|
||||
return list;
|
||||
@ -269,7 +270,7 @@ test_initializer python_types([](py::module &m) {
|
||||
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")(*o.attr("begin_end"));
|
||||
@ -279,13 +280,13 @@ test_initializer python_types([](py::module &m) {
|
||||
|
||||
m.def("test_tuple_accessor", [](py::tuple existing_t) {
|
||||
try {
|
||||
existing_t[0] = py::cast(1);
|
||||
existing_t[0] = 1;
|
||||
} catch (const py::error_already_set &) {
|
||||
// --> Python system error
|
||||
// Only new tuples (refcount == 1) are mutable
|
||||
auto new_t = py::tuple(3);
|
||||
for (size_t i = 0; i < new_t.size(); ++i) {
|
||||
new_t[i] = py::cast(i);
|
||||
new_t[i] = i;
|
||||
}
|
||||
return new_t;
|
||||
}
|
||||
@ -294,15 +295,15 @@ test_initializer python_types([](py::module &m) {
|
||||
|
||||
m.def("test_accessor_assignment", []() {
|
||||
auto l = py::list(1);
|
||||
l[0] = py::cast(0);
|
||||
l[0] = 0;
|
||||
|
||||
auto d = py::dict();
|
||||
d["get"] = l[0];
|
||||
auto var = l[0];
|
||||
d["deferred_get"] = var;
|
||||
l[0] = py::cast(1);
|
||||
l[0] = 1;
|
||||
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["var"] = var;
|
||||
|
||||
@ -338,8 +339,8 @@ test_initializer python_types([](py::module &m) {
|
||||
}, py::arg_v("x", std::experimental::nullopt, "None"));
|
||||
#endif
|
||||
|
||||
m.attr("has_optional") = py::cast(has_optional);
|
||||
m.attr("has_exp_optional") = py::cast(has_exp_optional);
|
||||
m.attr("has_optional") = has_optional;
|
||||
m.attr("has_exp_optional") = has_exp_optional;
|
||||
|
||||
m.def("test_default_constructors", []() {
|
||||
return py::dict(
|
||||
@ -389,4 +390,41 @@ test_initializer python_types([](py::module &m) {
|
||||
py::class_<MoveOutContainer>(m, "MoveOutContainer")
|
||||
.def(py::init<>())
|
||||
.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:
|
||||
set_result = instance.get_set()
|
||||
set_result.add('key3')
|
||||
set_result.add('key4')
|
||||
instance.print_set(set_result)
|
||||
assert capture.unordered == """
|
||||
key: key1
|
||||
key: key2
|
||||
key: key3
|
||||
key: key4
|
||||
"""
|
||||
with capture:
|
||||
set_result = instance.get_set2()
|
||||
@ -386,3 +387,16 @@ def test_move_out_container():
|
||||
c = MoveOutContainer()
|
||||
moved_out_list = c.move_list
|
||||
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