Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create sequences using initializer list #2451

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
24 changes: 24 additions & 0 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "detail/common.h"
#include "buffer_info.h"
#include <initializer_list>
#include <utility>
#include <type_traits>

Expand Down Expand Up @@ -1213,9 +1214,21 @@ class capsule : public object {
class tuple : public object {
public:
PYBIND11_OBJECT_CVT(tuple, object, PyTuple_Check, PySequence_Tuple)
/** \rst
Creates an empty ``tuple`` with given ``size`` to be later filled in.
\endrst */
explicit tuple(size_t size = 0) : object(PyTuple_New((ssize_t) size), stolen_t{}) {
if (!m_ptr) pybind11_fail("Could not allocate tuple object!");
}
/** \rst
Creates a ``tuple`` from an initializer list of object instances.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe

Suggested change
Creates a ``tuple`` from an initializer list of object instances.
Creates a ``tuple`` from an initializer list of ``py::object`` instances.

just to be explicit? In addition, the added documentation on make_tuple in #2840 could be augmented with some remarks on the new ways to initialize a tuple. I didn't bother to document list and set there, because they are mutable (pybind11 offers ways to modify tuples, but that's besides the point).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Explicit is better than implicit.

\endrst */
explicit tuple(std::initializer_list<object> init_list) : tuple(init_list.size()) {
detail::accessor_policies::tuple_item::key_type index{0};
for (const pybind11::object& item : init_list)
detail::tuple_accessor(*this, index++) = item;
}

size_t size() const { return (size_t) PyTuple_Size(m_ptr); }
bool empty() const { return size() == 0; }
detail::tuple_accessor operator[](size_t index) const { return {*this, index}; }
Expand Down Expand Up @@ -1276,6 +1289,12 @@ class list : public object {
explicit list(size_t size = 0) : object(PyList_New((ssize_t) size), stolen_t{}) {
if (!m_ptr) pybind11_fail("Could not allocate list object!");
}
explicit list(std::initializer_list<object> init_list) : list(init_list.size()) {
detail::accessor_policies::list_item::key_type index {0};
for (const pybind11::object& item : init_list)
detail::list_accessor(*this, index++) = item;
}

size_t size() const { return (size_t) PyList_Size(m_ptr); }
bool empty() const { return size() == 0; }
detail::list_accessor operator[](size_t index) const { return {*this, index}; }
Expand All @@ -1300,6 +1319,11 @@ class set : public object {
set() : object(PySet_New(nullptr), stolen_t{}) {
if (!m_ptr) pybind11_fail("Could not allocate set object!");
}
explicit set(std::initializer_list<object> init_list): set() {
for (const object& item : init_list)
PySet_Add(m_ptr, item.ptr());
}

size_t size() const { return (size_t) PySet_Size(m_ptr); }
bool empty() const { return size() == 0; }
template <typename T> bool add(T &&val) const {
Expand Down
40 changes: 40 additions & 0 deletions tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@

#include "pybind11_tests.h"

#include <array>
#include <deque>
#include <list>
#include <set>
#include <string>
#include <vector>

Comment on lines +12 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <array>
#include <deque>
#include <list>
#include <set>
#include <string>
#include <vector>

I assume these are leftovers?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but leave the #include <string> as strings are used.


TEST_SUBMODULE(pytypes, m) {
// test_int
Expand Down Expand Up @@ -237,6 +244,39 @@ TEST_SUBMODULE(pytypes, m) {
);
});

// Some python containers can also be constructed using an initializer list
m.def("initializer_list", []() {
return py::dict(
"tuple_ints"_a = py::tuple{py::int_(1), py::int_(2), py::int_(3)},
"tuple_floats"_a = py::tuple{py::float_(2.2), py::float_(3.1), py::float_(4.5), py::float_(5.4)},
"list_ints"_a = py::list{py::int_(1), py::int_(2), py::int_(3)},
"list_floats"_a = py::list{py::float_(2.2), py::float_(3.1), py::float_(4.5), py::float_(5.4)},
"tuple_objects"_a = py::tuple{
py::int_(321),
py::float_(123.3),
"somestring"_s,
py::tuple({py::int_(1), py::float_(3.3)}),
py::list({py::float_(5.5), py::int_(12)}),
py::dict("k_s"_a="v1", "k_i"_a=12, "k_f"_a=12.1)
},
"list_objects"_a = py::list{
py::int_(321),
py::float_(123.3),
"somestring"_s,
py::tuple({py::int_(1), py::float_(3.3)}),
py::list({py::float_(5.5), py::int_(12)}),
py::dict("k_s"_a="v1", "k_i"_a=12, "k_f"_a=12.1)
},
// Tuples are hashable, lists and dicts are not
"set_objects"_a = py::set{
py::int_(321),
py::float_(123.3),
"somestring"_s,
py::tuple({py::int_(1), py::float_(3.3)})
}
);
});

m.def("convert_to_pybind11_str", [](py::object o) { return py::str(o); });

m.def("get_implicit_casting", []() {
Expand Down
14 changes: 14 additions & 0 deletions tests/test_pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ def test_constructors():
for k in noconv2:
assert noconv2[k] is expected[k]

init_list = m.initializer_list()
assert init_list["tuple_ints"] == (1, 2, 3)
assert init_list["tuple_floats"] == (2.2, 3.1, 4.5, 5.4)
assert init_list["list_ints"] == [1, 2, 3]
assert init_list["list_floats"] == [2.2, 3.1, 4.5, 5.4]

assert init_list["tuple_objects"] == (
321, 123.3, "somestring", (1, 3.3), [5.5, 12], {"k_s": "v1", "k_i": 12, "k_f": 12.1}
)
assert init_list["list_objects"] == [
321, 123.3, "somestring", (1, 3.3), [5.5, 12], {"k_s": "v1", "k_i": 12, "k_f": 12.1}
]
assert init_list["set_objects"] == {321, 123.3, "somestring", (1, 3.3)}


def test_pybind11_str_raw_str():
# specifically to exercise pybind11::str::raw_str
Expand Down