diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 9c6bc32b8..3e81d4bb5 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1338,7 +1338,7 @@ NAMESPACE_BEGIN(detail) PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) { auto strings = tuple(args.size()); for (size_t i = 0; i < args.size(); ++i) { - strings[i] = args[i].cast().str(); + strings[i] = args[i].str(); } auto sep = kwargs.contains("sep") ? kwargs["sep"] : cast(" "); auto line = sep.attr("join")(strings); diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index a5e26345c..8fa01ff86 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -29,10 +29,14 @@ namespace accessor_policies { struct obj_attr; struct str_attr; struct generic_item; + struct list_item; + struct tuple_item; } using obj_attr_accessor = accessor; using str_attr_accessor = accessor; using item_accessor = accessor; +using list_accessor = accessor; +using tuple_accessor = accessor; /// Tag and check to identify a class which implements the Python object API class pyobject_tag { }; @@ -241,58 +245,42 @@ struct generic_item { if (PyObject_SetItem(obj.ptr(), key.ptr(), val.ptr()) != 0) { throw error_already_set(); } } }; + +struct list_item { + using key_type = size_t; + + static object get(handle obj, size_t index) { + PyObject *result = PyList_GetItem(obj.ptr(), static_cast(index)); + if (!result) { throw error_already_set(); } + return {result, true}; + } + + static void set(handle obj, size_t index, handle val) { + // PyList_SetItem steals a reference to 'val' + if (PyList_SetItem(obj.ptr(), static_cast(index), val.inc_ref().ptr()) != 0) { + throw error_already_set(); + } + } +}; + +struct tuple_item { + using key_type = size_t; + + static object get(handle obj, size_t index) { + PyObject *result = PyTuple_GetItem(obj.ptr(), static_cast(index)); + if (!result) { throw error_already_set(); } + return {result, true}; + } + + static void set(handle obj, size_t index, handle val) { + // PyTuple_SetItem steals a reference to 'val' + if (PyTuple_SetItem(obj.ptr(), static_cast(index), val.inc_ref().ptr()) != 0) { + throw error_already_set(); + } + } +}; NAMESPACE_END(accessor_policies) -struct list_accessor { -public: - list_accessor(handle list, size_t index) : list(list), index(index) { } - - void operator=(list_accessor o) { return operator=(object(o)); } - - void operator=(const handle &o) { - // PyList_SetItem steals a reference to 'o' - if (PyList_SetItem(list.ptr(), (ssize_t) index, o.inc_ref().ptr()) < 0) - pybind11_fail("Unable to assign value in Python list!"); - } - - operator object() const { - PyObject *result = PyList_GetItem(list.ptr(), (ssize_t) index); - if (!result) - pybind11_fail("Unable to retrieve value from Python list!"); - return object(result, true); - } - - template T cast() const { return operator object().cast(); } -private: - handle list; - size_t index; -}; - -struct tuple_accessor { -public: - tuple_accessor(handle tuple, size_t index) : tuple(tuple), index(index) { } - - void operator=(tuple_accessor o) { return operator=(object(o)); } - - void operator=(const handle &o) { - // PyTuple_SetItem steals a referenceto 'o' - if (PyTuple_SetItem(tuple.ptr(), (ssize_t) index, o.inc_ref().ptr()) < 0) - pybind11_fail("Unable to assign value in Python tuple!"); - } - - operator object() const { - PyObject *result = PyTuple_GetItem(tuple.ptr(), (ssize_t) index); - if (!result) - pybind11_fail("Unable to retrieve value from Python tuple!"); - return object(result, true); - } - - template T cast() const { return operator object().cast(); } -private: - handle tuple; - size_t index; -}; - struct dict_iterator { public: dict_iterator(handle dict = handle(), ssize_t pos = -1) : dict(dict), pos(pos) { } @@ -647,7 +635,7 @@ public: if (!m_ptr) pybind11_fail("Could not allocate tuple object!"); } size_t size() const { return (size_t) PyTuple_Size(m_ptr); } - detail::tuple_accessor operator[](size_t index) const { return detail::tuple_accessor(*this, index); } + detail::tuple_accessor operator[](size_t index) const { return {*this, index}; } }; class dict : public object { @@ -677,7 +665,7 @@ public: if (!m_ptr) pybind11_fail("Could not allocate list object!"); } size_t size() const { return (size_t) PyList_Size(m_ptr); } - detail::list_accessor operator[](size_t index) const { return detail::list_accessor(*this, index); } + detail::list_accessor operator[](size_t index) const { return {*this, index}; } void append(handle h) const { PyList_Append(m_ptr, h.ptr()); } }; diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 4ab90e63a..fabc78e8e 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -60,7 +60,7 @@ public: py::list get_list() { py::list list; list.append(py::str("value")); - py::print("Entry at position 0:", py::object(list[0])); + py::print("Entry at position 0:", list[0]); list[0] = py::str("overwritten"); return list; } @@ -257,4 +257,19 @@ test_initializer python_types([](py::module &m) { return d; }); + + m.def("test_tuple_accessor", [](py::tuple existing_t) { + try { + existing_t[0] = py::cast(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); + } + return new_t; + } + return py::tuple(); + }); }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 4f2cdb2c3..a6cf1fe03 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -251,7 +251,7 @@ def test_dict_api(): def test_accessors(): - from pybind11_tests import test_accessor_api + from pybind11_tests import test_accessor_api, test_tuple_accessor class SubTestObject: attr_obj = 1 @@ -278,3 +278,5 @@ def test_accessors(): assert d["is_none"] is False assert d["operator()"] == 2 assert d["operator*"] == 7 + + assert test_tuple_accessor(tuple()) == (0, 1, 2)