Skip to content

Expose cached properties of slotted classes via __attrs_cached_properties__ - #1613

Open
cjchanh wants to merge 1 commit into
python-attrs:mainfrom
cjchanh:expose-cached-properties
Open

Expose cached properties of slotted classes via __attrs_cached_properties__#1613
cjchanh wants to merge 1 commit into
python-attrs:mainfrom
cjchanh:expose-cached-properties

Conversation

@cjchanh

@cjchanh cjchanh commented Aug 19, 2026

Copy link
Copy Markdown

Expose cached properties of slotted classes via __attrs_cached_properties__

Fixes #1532.

Summary

Slotted classes created by attrs now carry a public __attrs_cached_properties__ class attribute: a plain dict that maps the names of the cached_property functions defined on the class to the original functions.

This gives tooling (documentation generators, debuggers, serializers, framework code) a stable, documented way to introspect cached properties on attrs slotted classes — something that is currently only possible by reading the private generated __getattr__ closure.

Public surface choice

  • Attribute name__attrs_cached_properties__ follows the existing __attrs_* dunder family (__attrs_attrs__, __attrs_own_setattr__, __attrs_init__, …), signalling that it is part of the attrs runtime contract on generated classes.
  • Plain dict, never evaluates — the mapping is the same dict that the generated __getattr__ reads for cached-property dispatch, so accessing the attribute copies no state and calls none of the functions. It contains the original function objects (cached_property.func), which never triggers evaluation. This is documented in the docs note.
  • Own-only scope — like the internal machinery, the mapping only contains cached properties defined on the class itself; inherited ones are excluded. The docs explain how to collect inherited ones by walking __mro__.
  • Slotted classes only — the attribute is set inside _ClassBuilder._create_slots_class(), so non-slotted classes are completely unaffected (they don't grow the attribute at all). Every slotted class gets it, including those with no cached properties (an empty dict), so consumers never need a hasattr check.
  • Design note — because the public dict is the internal dispatch dict, user mutation of it can change class behavior; this is intentional and disclosed in the docs (it also guarantees the public view always matches runtime behavior).

Implementation

  • src/attr/_make.py — one assignment in _create_slots_class() exposing the existing cached_properties dict as a class attribute.
  • src/attr/__init__.py — unchanged: the new surface is a class attribute on generated classes, so no module-level export is needed.

Docs & changelog

  • docs/extending.md — new "Cached Properties" section with a runnable doctest, own-only/MRO guidance, and a note about the shared mapping.
  • changelog.d/1532.change.md — towncrier fragment (validated with towncrier build --draft).

Tests

Five new tests in tests/test_slots.py:

  • mapping contents and identity of the original functions,
  • accessing the attribute never evaluates the cached properties (call-count guard),
  • slotted classes without cached properties get an empty dict,
  • non-slotted classes do not get the attribute,
  • inherited cached properties are excluded (own-only).

Verification

All green on matrix/attrs1532-arm6 (base: 30fd617, feature commit a440453):

$ .venv/bin/pip install -e .            # attrs 26.1.1.dev72, editable
$ .venv/bin/python -m pytest -n auto -q
# 1412 passed, 4 skipped (env-only), 2 xfailed (expected) in 8.03s

$ .venv/bin/pre-commit run --all-files
# interrogate, codespell, validate-pyproject, trailing-whitespace,
# end-of-file-fixer, check-toml, check-yaml — all Passed

$ .venv/bin/towncrier build --draft --version main
# fragment renders under "### Changes" with [#1532] link

$ .venv/bin/mypy src/attr/_make.py src/attr/__init__.py
# no errors in the changed region (pre-existing baseline errors in
# untouched code are unrelated; mypy is not part of pre-commit)

Not run: docs build (Sphinx) and pyright/pyrefly typing gates — out of scope here; the change is runtime-only with no typing surface. Doctest was executed manually and matches the documented output shape.

…ties__

Slotted classes now carry a public __attrs_cached_properties__ class
attribute: a plain dict mapping the names of the cached_property
functions defined on the class to the original functions.  It is the
same mapping the generated __getattr__ uses, so accessing it never
evaluates the properties.  Non-slotted classes are unaffected.

Documented in docs/extending.md and changelog.d/1532.change.md.

Fixes python-attrs#1532
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose cached property functions of slots classes via public API

1 participant