Skip to content

Add native __contains__ to Container, Table and InlineTable#487

Merged
frostming merged 2 commits into
python-poetry:masterfrom
AstekGroup:perf-key-container-fastpaths
Jun 5, 2026
Merged

Add native __contains__ to Container, Table and InlineTable#487
frostming merged 2 commits into
python-poetry:masterfrom
AstekGroup:perf-key-container-fastpaths

Conversation

@tfoutrein

@tfoutrein tfoutrein commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

First of the small, self-contained, conformance-preserving performance changes discussed in #483 — now also covering Table / InlineTable per @frostming's review question.

Both Container and the table types (Table, InlineTable, via AbstractTable) inherit __contains__ from MutableMapping, so key in x goes through self[key]__getitem__item(), which resolves and returns the value — and constructs a NonExistentKey exception on every absent key — only to discard it.

Container gets a native __contains__ that resolves the key exactly as item() does (strSingleKey; a non-str/non-Key argument still raises TypeError) and probes self._map directly. For an out-of-order table (a tuple index) it still builds the OutOfOrderTableProxy, so its validation runs exactly as before. This also speeds up Container.__setitem__, which does if key in self on every assignment.

Table / InlineTable get a native AbstractTable.__contains__ that delegates to the inner container (return key in self._value). The inherited mixin never reached the native Container.__contains__ (it went through __getitem__), so the table types didn't benefit transitively; delegating fixes that while still building the OutOfOrderTableProxy for an out-of-order entry, so validation is unchanged.

Behaviour — no change

Beyond the existing suite, I ran a differential in check (present / absent / out-of-order / dotted-string / Key-vs-str / non-str keys) comparing this branch against master, for both Container and the table types: identical results, including the TypeError on a non-string key and the out-of-order proxy path. A regression test on an out-of-order super-table is included.

Benchmark

Drift-immune A/B in a single process (native __contains__ vs the inherited __getitem__-based path, interleaved so thermal drift cancels), CPython 3.11:

Container (mixed present + absent keys):
  inherited via __getitem__: ~610 ms
  native __contains__:       ~520 ms     → ~1.17x faster

Table / InlineTable membership (key in table):
  present key: ~1.2x faster
  absent key:  ~1.8x faster   (the miss path is where the discarded
                               NonExistentKey construction dominates)

Verification

  • pytest tests/ (incl. the toml-test conformance submodule): 972 passed (969 on master + 3 new membership tests), no regressions.
  • ruff check + ruff format --check: clean.
  • mypy (1.19.1): no new errors. The single # type: ignore[arg-type] (in Container.__contains__) mirrors the existing SingleKey(key) call in item().
  • The 3 new membership tests were validated by focused mutation testing — every mutant of the new __contains__ is killed, and an equivalent mutant correctly survives.

Context: this is harness-assisted work — every change is individually benchmarked and gated (full test suite incl. conformance, a differential guard, ruff + mypy) before it can land, and I review and stand behind each PR personally. Happy to slice the rest of #483 into further small PRs — e.g. the same native __contains__ for OutOfOrderTableProxy (a separate base class), or the bulk char-scanning / header-cache work.

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 python-poetry#483.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frostming

Copy link
Copy Markdown
Contributor

LGTM, do you also look at Table? does it also need a native impl?

Table and InlineTable inherit __contains__ from the MutableMapping mixin,
which answers `key in table` by calling self[key] -- resolving the value
through the inner container and building a NonExistentKey on every absent
key -- only to discard it.

Add a native AbstractTable.__contains__ (covering both Table and
InlineTable) that delegates straight to the inner container, so membership
is answered from the now-native Container.__contains__ while still building
the OutOfOrderTableProxy for an out-of-order entry (validation unchanged).
@tfoutrein tfoutrein changed the title Add a native Container.__contains__ Add native __contains__ to Container, Table and InlineTable Jun 4, 2026
@tfoutrein

Copy link
Copy Markdown
Contributor Author

Good call — yes. Table and InlineTable inherit __contains__ from the same MutableMapping mixin, and it didn't benefit from the native Container.__contains__ transitively: the mixin goes through self[key]AbstractTable.__getitem__Container.__getitem__, never Container.__contains__.

I've pushed it to this PR — a one-liner on AbstractTable, so it covers both Table and InlineTable:

def __contains__(self, key: object) -> bool:
    return key in self._value

Delegating to the inner container picks up the fast _map lookup and still builds the OutOfOrderTableProxy for an out-of-order entry, so validation (including the KeyAlreadyPresent it can raise) is preserved exactly. Iso-functional across present / absent / Key-vs-str / non-str (TypeError) / out-of-order / InlineTable; full suite + conformance stays green (972), with a regression test on an out-of-order super-table. Membership micro-bench ≈ 1.2x (present) / 1.8x (absent).

OutOfOrderTableProxy has the same mixin but is a separate base class, so it isn't covered here — happy to give it the same one-liner in a follow-up if you'd like. I've updated the PR title/description to match.

@frostming
frostming merged commit 5de4830 into python-poetry:master Jun 5, 2026
25 checks passed
@frostming frostming added the perf performance enhancement label Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

perf performance enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants