feat: add .keys and .values to bind_map (#3310)

* Add `.keys` and `.values` to bind_map

Both of these implement views (rather than just iterators), and `.items`
is also upgraded to a view. In practical terms, this allows a view to be
iterated multiple times and have its size taken, neither of which works
with an iterator.

The views implement `__len__`, `__iter__`, and the keys view implements
`__contains__`. Testing membership also works in item and value views
because Python falls back to iteration. This won't be optimal
for item values since it's linear rather than O(log n) or O(1), but I
didn't fancy trying to get all the corner cases to match Python
behaviour (tuple of wrong types, wrong length tuple, not a tuple etc).

Missing relative to Python dictionary views is `__reversed__` (only
added to Python in 3.8). Implementing that could break code that binds
custom map classes which don't provide `rbegin`/`rend` (at least without
doing clever things with SFINAE), so I've not tried.

The size increase on my system is 131072 bytes, which is rather large
(5%) but also suspiciously round (2^17) and makes me suspect some
quantisation effect.

* bind_map: support any object in __contains__

Add extra overload of `__contains__` (for both the map itself and
KeysView) which takes an arbitrary object and returns false.

* Take py::object by const reference in __contains__

To keep clang-tidy happy.

* Removing stray `py::` (detected via interactive testing in Google environment).

Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com>
This commit is contained in:
Bruce Merry
2021-10-01 15:24:36 +02:00
committed by GitHub
parent b4e1ab8caa
commit b3573ac961
2 changed files with 104 additions and 4 deletions

View File

@@ -160,15 +160,43 @@ def test_map_string_double():
mm["b"] = 2.5
assert list(mm) == ["a", "b"]
assert list(mm.items()) == [("a", 1), ("b", 2.5)]
assert str(mm) == "MapStringDouble{a: 1, b: 2.5}"
assert "b" in mm
assert "c" not in mm
assert 123 not in mm
# Check that keys, values, items are views, not merely iterable
keys = mm.keys()
values = mm.values()
items = mm.items()
assert list(keys) == ["a", "b"]
assert len(keys) == 2
assert "a" in keys
assert "c" not in keys
assert 123 not in keys
assert list(items) == [("a", 1), ("b", 2.5)]
assert len(items) == 2
assert ("b", 2.5) in items
assert "hello" not in items
assert ("b", 2.5, None) not in items
assert list(values) == [1, 2.5]
assert len(values) == 2
assert 1 in values
assert 2 not in values
# Check that views update when the map is updated
mm["c"] = -1
assert list(keys) == ["a", "b", "c"]
assert list(values) == [1, 2.5, -1]
assert list(items) == [("a", 1), ("b", 2.5), ("c", -1)]
um = m.UnorderedMapStringDouble()
um["ua"] = 1.1
um["ub"] = 2.6
assert sorted(list(um)) == ["ua", "ub"]
assert list(um.keys()) == list(um)
assert sorted(list(um.items())) == [("ua", 1.1), ("ub", 2.6)]
assert list(zip(um.keys(), um.values())) == list(um.items())
assert "UnorderedMapStringDouble" in str(um)