From a12713acff83b0222917719890d8f9a2f03c9bf0 Mon Sep 17 00:00:00 2001 From: Thomas Foutrein Date: Wed, 3 Jun 2026 15:34:56 +0200 Subject: [PATCH] Add a native Container.__contains__ Container inherits __contains__ from MutableMapping, so `key in container` goes through __getitem__ -> item(), which resolves and returns the value (and constructs a NonExistentKey on every absent key) only to discard it. Resolve the key exactly as item() does (str -> SingleKey; a non-str/non-Key argument still raises TypeError) and probe _map directly. For an out-of-order table the OutOfOrderTableProxy is still built so its validation runs as before. This also speeds up __setitem__, which does `if key in self` on every assignment. Behaviour-identical: 969 tests pass (incl. toml-test); a differential `in` check over present/absent/out-of-order/dotted/non-str keys matches master exactly. ~1.17x faster on membership (drift-immune A/B). Part of #483. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 ++++ tomlkit/container.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c14e764..8c44112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [unreleased] +### Changed + +- Speed up membership tests (`key in container`) with a native `Container.__contains__`, avoiding the inherited `MutableMapping` round-trip through `__getitem__` (which resolves the value and builds an exception on every absent key). ([#483](https://github.com/python-poetry/tomlkit/issues/483)) + ### Fixed - Fix invalid serialization with a duplicated comma when removing a non-edge element from a parsed inline table. ([#486](https://github.com/python-poetry/tomlkit/pull/486)) diff --git a/tomlkit/container.py b/tomlkit/container.py index 4387471..b6a91ca 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -714,6 +714,23 @@ def __getitem__(self, key: Key | str) -> Any: return item + def __contains__(self, key: object) -> bool: + # Native membership test. The inherited ``MutableMapping.__contains__`` + # resolves the value via ``__getitem__``/``item()`` (and builds a + # ``NonExistentKey`` on every absent key) only to discard it. Resolve the + # key the same way ``item()`` does -- ``str`` becomes a ``SingleKey`` + # (a non-str/non-``Key`` argument still raises ``TypeError``) -- then + # probe ``_map`` directly. For an out-of-order table the proxy is still + # built so its validation runs exactly as before. + if not isinstance(key, Key): + key = SingleKey(key) # type: ignore[arg-type] + idx = self._map.get(key) + if idx is None: + return False + if isinstance(idx, tuple): + OutOfOrderTableProxy(self, idx) + return True + def __setitem__(self, key: Key | str, value: Any) -> None: if key in self: old_key = next(filter(lambda k: k == key, self._map))