mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +00:00
Add anyset & frozenset, enable copying (cast) to std::set (#3901)
* Add frozenset, and allow it cast to std::set For the reverse direction, std::set still casts to set. This is in concordance with the behavior for sequence containers, where e.g. tuple casts to std::vector but std::vector casts to list. Extracted from #3886. * Rename set_base to any_set to match Python C API since this will be part of pybind11 public API * PR: static_cast, anyset * Add tests for frozenset and rename anyset methods * Remove frozenset default ctor, add tests Making frozenset non-default constructible means that we need to adjust pyobject_caster to not require that its value is default constructible, by initializing value to a nil handle. This also allows writing C++ functions taking anyset, and is arguably a performance improvement, since there is no need to allocate an object that will just be replaced by load. Add some more tests, including anyset::empty, anyset::size, set::add and set::clear. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add rationale to `pyobject_caster` default ctor * Remove ineffectual protected: access control Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
9a16e55ad2
commit
68a0b2dfd8
@ -908,6 +908,14 @@ struct handle_type_name<kwargs> {
|
|||||||
|
|
||||||
template <typename type>
|
template <typename type>
|
||||||
struct pyobject_caster {
|
struct pyobject_caster {
|
||||||
|
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>
|
||||||
|
pyobject_caster() : value() {}
|
||||||
|
|
||||||
|
// `type` may not be default constructible (e.g. frozenset, anyset). Initializing `value`
|
||||||
|
// to a nil handle is safe since it will only be accessed if `load` succeeds.
|
||||||
|
template <typename T = type, enable_if_t<std::is_base_of<object, T>::value, int> = 0>
|
||||||
|
pyobject_caster() : value(reinterpret_steal<type>(handle())) {}
|
||||||
|
|
||||||
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>
|
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>
|
||||||
bool load(handle src, bool /* convert */) {
|
bool load(handle src, bool /* convert */) {
|
||||||
value = src;
|
value = src;
|
||||||
|
@ -1784,25 +1784,35 @@ class kwargs : public dict {
|
|||||||
PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check)
|
PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check)
|
||||||
};
|
};
|
||||||
|
|
||||||
class set : public object {
|
class anyset : public object {
|
||||||
public:
|
public:
|
||||||
PYBIND11_OBJECT_CVT(set, object, PySet_Check, PySet_New)
|
PYBIND11_OBJECT(anyset, object, PyAnySet_Check)
|
||||||
set() : object(PySet_New(nullptr), stolen_t{}) {
|
size_t size() const { return static_cast<size_t>(PySet_Size(m_ptr)); }
|
||||||
|
bool empty() const { return size() == 0; }
|
||||||
|
template <typename T>
|
||||||
|
bool contains(T &&val) const {
|
||||||
|
return PySet_Contains(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) == 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class set : public anyset {
|
||||||
|
public:
|
||||||
|
PYBIND11_OBJECT_CVT(set, anyset, PySet_Check, PySet_New)
|
||||||
|
set() : anyset(PySet_New(nullptr), stolen_t{}) {
|
||||||
if (!m_ptr) {
|
if (!m_ptr) {
|
||||||
pybind11_fail("Could not allocate set object!");
|
pybind11_fail("Could not allocate set object!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size_t size() const { return (size_t) PySet_Size(m_ptr); }
|
|
||||||
bool empty() const { return size() == 0; }
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool add(T &&val) /* py-non-const */ {
|
bool add(T &&val) /* py-non-const */ {
|
||||||
return PySet_Add(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) == 0;
|
return PySet_Add(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) == 0;
|
||||||
}
|
}
|
||||||
void clear() /* py-non-const */ { PySet_Clear(m_ptr); }
|
void clear() /* py-non-const */ { PySet_Clear(m_ptr); }
|
||||||
template <typename T>
|
};
|
||||||
bool contains(T &&val) const {
|
|
||||||
return PySet_Contains(m_ptr, detail::object_or_cast(std::forward<T>(val)).ptr()) == 1;
|
class frozenset : public anyset {
|
||||||
}
|
public:
|
||||||
|
PYBIND11_OBJECT_CVT(frozenset, anyset, PyFrozenSet_Check, PyFrozenSet_New)
|
||||||
};
|
};
|
||||||
|
|
||||||
class function : public object {
|
class function : public object {
|
||||||
|
@ -55,10 +55,10 @@ struct set_caster {
|
|||||||
using key_conv = make_caster<Key>;
|
using key_conv = make_caster<Key>;
|
||||||
|
|
||||||
bool load(handle src, bool convert) {
|
bool load(handle src, bool convert) {
|
||||||
if (!isinstance<pybind11::set>(src)) {
|
if (!isinstance<anyset>(src)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto s = reinterpret_borrow<pybind11::set>(src);
|
auto s = reinterpret_borrow<anyset>(src);
|
||||||
value.clear();
|
value.clear();
|
||||||
for (auto entry : s) {
|
for (auto entry : s) {
|
||||||
key_conv conv;
|
key_conv conv;
|
||||||
|
@ -75,7 +75,7 @@ TEST_SUBMODULE(pytypes, m) {
|
|||||||
m.def("get_none", [] { return py::none(); });
|
m.def("get_none", [] { return py::none(); });
|
||||||
m.def("print_none", [](const py::none &none) { py::print("none: {}"_s.format(none)); });
|
m.def("print_none", [](const py::none &none) { py::print("none: {}"_s.format(none)); });
|
||||||
|
|
||||||
// test_set
|
// test_set, test_frozenset
|
||||||
m.def("get_set", []() {
|
m.def("get_set", []() {
|
||||||
py::set set;
|
py::set set;
|
||||||
set.add(py::str("key1"));
|
set.add(py::str("key1"));
|
||||||
@ -83,14 +83,26 @@ TEST_SUBMODULE(pytypes, m) {
|
|||||||
set.add(std::string("key3"));
|
set.add(std::string("key3"));
|
||||||
return set;
|
return set;
|
||||||
});
|
});
|
||||||
m.def("print_set", [](const py::set &set) {
|
m.def("get_frozenset", []() {
|
||||||
|
py::set set;
|
||||||
|
set.add(py::str("key1"));
|
||||||
|
set.add("key2");
|
||||||
|
set.add(std::string("key3"));
|
||||||
|
return py::frozenset(set);
|
||||||
|
});
|
||||||
|
m.def("print_anyset", [](const py::anyset &set) {
|
||||||
for (auto item : set) {
|
for (auto item : set) {
|
||||||
py::print("key:", item);
|
py::print("key:", item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m.def("set_contains",
|
m.def("anyset_size", [](const py::anyset &set) { return set.size(); });
|
||||||
[](const py::set &set, const py::object &key) { return set.contains(key); });
|
m.def("anyset_empty", [](const py::anyset &set) { return set.empty(); });
|
||||||
m.def("set_contains", [](const py::set &set, const char *key) { return set.contains(key); });
|
m.def("anyset_contains",
|
||||||
|
[](const py::anyset &set, const py::object &key) { return set.contains(key); });
|
||||||
|
m.def("anyset_contains",
|
||||||
|
[](const py::anyset &set, const char *key) { return set.contains(key); });
|
||||||
|
m.def("set_add", [](py::set &set, const py::object &key) { set.add(key); });
|
||||||
|
m.def("set_clear", [](py::set &set) { set.clear(); });
|
||||||
|
|
||||||
// test_dict
|
// test_dict
|
||||||
m.def("get_dict", []() { return py::dict("key"_a = "value"); });
|
m.def("get_dict", []() { return py::dict("key"_a = "value"); });
|
||||||
@ -310,6 +322,7 @@ TEST_SUBMODULE(pytypes, m) {
|
|||||||
"list"_a = py::list(d["list"]),
|
"list"_a = py::list(d["list"]),
|
||||||
"dict"_a = py::dict(d["dict"]),
|
"dict"_a = py::dict(d["dict"]),
|
||||||
"set"_a = py::set(d["set"]),
|
"set"_a = py::set(d["set"]),
|
||||||
|
"frozenset"_a = py::frozenset(d["frozenset"]),
|
||||||
"memoryview"_a = py::memoryview(d["memoryview"]));
|
"memoryview"_a = py::memoryview(d["memoryview"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -325,6 +338,7 @@ TEST_SUBMODULE(pytypes, m) {
|
|||||||
"list"_a = d["list"].cast<py::list>(),
|
"list"_a = d["list"].cast<py::list>(),
|
||||||
"dict"_a = d["dict"].cast<py::dict>(),
|
"dict"_a = d["dict"].cast<py::dict>(),
|
||||||
"set"_a = d["set"].cast<py::set>(),
|
"set"_a = d["set"].cast<py::set>(),
|
||||||
|
"frozenset"_a = d["frozenset"].cast<py::frozenset>(),
|
||||||
"memoryview"_a = d["memoryview"].cast<py::memoryview>());
|
"memoryview"_a = d["memoryview"].cast<py::memoryview>());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -66,11 +66,12 @@ def test_none(capture, doc):
|
|||||||
|
|
||||||
def test_set(capture, doc):
|
def test_set(capture, doc):
|
||||||
s = m.get_set()
|
s = m.get_set()
|
||||||
|
assert isinstance(s, set)
|
||||||
assert s == {"key1", "key2", "key3"}
|
assert s == {"key1", "key2", "key3"}
|
||||||
|
|
||||||
|
s.add("key4")
|
||||||
with capture:
|
with capture:
|
||||||
s.add("key4")
|
m.print_anyset(s)
|
||||||
m.print_set(s)
|
|
||||||
assert (
|
assert (
|
||||||
capture.unordered
|
capture.unordered
|
||||||
== """
|
== """
|
||||||
@ -81,12 +82,43 @@ def test_set(capture, doc):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
assert not m.set_contains(set(), 42)
|
m.set_add(s, "key5")
|
||||||
assert m.set_contains({42}, 42)
|
assert m.anyset_size(s) == 5
|
||||||
assert m.set_contains({"foo"}, "foo")
|
|
||||||
|
|
||||||
assert doc(m.get_list) == "get_list() -> list"
|
m.set_clear(s)
|
||||||
assert doc(m.print_list) == "print_list(arg0: list) -> None"
|
assert m.anyset_empty(s)
|
||||||
|
|
||||||
|
assert not m.anyset_contains(set(), 42)
|
||||||
|
assert m.anyset_contains({42}, 42)
|
||||||
|
assert m.anyset_contains({"foo"}, "foo")
|
||||||
|
|
||||||
|
assert doc(m.get_set) == "get_set() -> set"
|
||||||
|
assert doc(m.print_anyset) == "print_anyset(arg0: anyset) -> None"
|
||||||
|
|
||||||
|
|
||||||
|
def test_frozenset(capture, doc):
|
||||||
|
s = m.get_frozenset()
|
||||||
|
assert isinstance(s, frozenset)
|
||||||
|
assert s == frozenset({"key1", "key2", "key3"})
|
||||||
|
|
||||||
|
with capture:
|
||||||
|
m.print_anyset(s)
|
||||||
|
assert (
|
||||||
|
capture.unordered
|
||||||
|
== """
|
||||||
|
key: key1
|
||||||
|
key: key2
|
||||||
|
key: key3
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert m.anyset_size(s) == 3
|
||||||
|
assert not m.anyset_empty(s)
|
||||||
|
|
||||||
|
assert not m.anyset_contains(frozenset(), 42)
|
||||||
|
assert m.anyset_contains(frozenset({42}), 42)
|
||||||
|
assert m.anyset_contains(frozenset({"foo"}), "foo")
|
||||||
|
|
||||||
|
assert doc(m.get_frozenset) == "get_frozenset() -> frozenset"
|
||||||
|
|
||||||
|
|
||||||
def test_dict(capture, doc):
|
def test_dict(capture, doc):
|
||||||
@ -302,6 +334,7 @@ def test_constructors():
|
|||||||
list: range(3),
|
list: range(3),
|
||||||
dict: [("two", 2), ("one", 1), ("three", 3)],
|
dict: [("two", 2), ("one", 1), ("three", 3)],
|
||||||
set: [4, 4, 5, 6, 6, 6],
|
set: [4, 4, 5, 6, 6, 6],
|
||||||
|
frozenset: [4, 4, 5, 6, 6, 6],
|
||||||
memoryview: b"abc",
|
memoryview: b"abc",
|
||||||
}
|
}
|
||||||
inputs = {k.__name__: v for k, v in data.items()}
|
inputs = {k.__name__: v for k, v in data.items()}
|
||||||
|
@ -73,6 +73,7 @@ def test_set(doc):
|
|||||||
assert s == {"key1", "key2"}
|
assert s == {"key1", "key2"}
|
||||||
s.add("key3")
|
s.add("key3")
|
||||||
assert m.load_set(s)
|
assert m.load_set(s)
|
||||||
|
assert m.load_set(frozenset(s))
|
||||||
|
|
||||||
assert doc(m.cast_set) == "cast_set() -> Set[str]"
|
assert doc(m.cast_set) == "cast_set() -> Set[str]"
|
||||||
assert doc(m.load_set) == "load_set(arg0: Set[str]) -> bool"
|
assert doc(m.load_set) == "load_set(arg0: Set[str]) -> bool"
|
||||||
|
Loading…
Reference in New Issue
Block a user