Skip to content

Instantly share code, notes, and snippets.

@mara004
Last active July 23, 2026 07:22
Show Gist options
  • Select an option

  • Save mara004/fd944fe3a4a3fc514171e9cf617292d7 to your computer and use it in GitHub Desktop.

Select an option

Save mara004/fd944fe3a4a3fc514171e9cf617292d7 to your computer and use it in GitHub Desktop.
Cached properties with slots, via metaclass (Hacky variant)
# SPDX-FileCopyrightText: 2026 geisserml <geisserml@gmail.com>
# SPDX-License-Identifier: MPL-2.0
from typing import TYPE_CHECKING
from stl import cached_property
if TYPE_CHECKING:
from functools import cached_property
def _func_visitor(cp_map):
def visit(func):
cp_map[func.__name__] = func
return visit
def _cp_getattr(self, key):
value = self._cp_map_[key](self)
setattr(self, key, value)
return value
class _ClassBuidlerMeta (type):
@classmethod
def __prepare__(metacls, name, bases, use_slots=False, prefer_getattr=None, **kwargs):
nsp = {}
if prefer_getattr or use_slots:
nsp["_cp_map_"] = cp_map = {}
nsp["cached_property"] = _func_visitor(cp_map)
return nsp
def __new__(metacls, clsname, bases, nsp, use_slots=False, prefer_getattr=None, **kwargs):
if use_slots:
fields = nsp.get("_fields_")
nsp["__slots__"] = (*fields, *nsp["_cp_map_"].keys())
if prefer_getattr or use_slots:
nsp["__getattr__"] = _cp_getattr
for k in nsp["_cp_map_"].keys():
del nsp[k]
del nsp["cached_property"]
return super().__new__(metacls, clsname, bases, nsp, **kwargs)
class ClassBuilder (metaclass=_ClassBuidlerMeta):
# Child class slots effectively only work when all base classes are slotted, since otherwise there would be an accessible __dict__ attribute.
# That means we have to declare (empty) slots here. This does not negatively impact non-slotted child classes.
# See https://stackoverflow.com/a/28059785/15547292 and https://docs.python.org/3/reference/datamodel.html#slots for more background.
__slots__ = ()
# SPDX-FileCopyrightText: 2026 geisserml <geisserml@gmail.com>
# SPDX-License-Identifier: MPL-2.0
class cached_property:
def __init__(self, func):
self.func = func
self.assigned_name = None
self.__doc__ = func.__doc__
# Optional. On older Python versions (< 3.6) that do not call this hook, the func's __name__ will be used as fallback.
def __set_name__(self, cls, name):
if self.assigned_name is None:
self.assigned_name = name
else:
assert name == self.assigned_name, f"A cached property is tied to one attribute. You cannot assign to both {name!r} and {self.assigned_name!r}."
def __get__(self, obj, cls=None):
if obj is None:
return self
value = self.func(obj)
name = self.assigned_name or self.func.__name__
setattr(obj, name, value)
return value
# SPDX-FileCopyrightText: 2026 geisserml <geisserml@gmail.com>
# SPDX-License-Identifier: MPL-2.0
import sys
import uuid
from pathlib import Path
from class_kit_old import * # local
# USE_SLOTS, PREFER_GETATTR = False, None
# USE_SLOTS, PREFER_GETATTR = False, True
USE_SLOTS, PREFER_GETATTR = True, None
if USE_SLOTS:
def get_vars(obj):
return {k: getattr(d, k) for k in d._fields_}
else:
get_vars = vars # builtin
class TestData (ClassBuilder, use_slots=USE_SLOTS, prefer_getattr=PREFER_GETATTR):
_fields_ = ("file_name", )
def __init__(self, file_name):
self.file_name = file_name
@cached_property
def file_content(self):
print(f"Reading file {self.file_name}", file=sys.stderr)
return Path(self.file_name).read_text()
@cached_property
def unique_id(self):
print(f"Generating unique id", file=sys.stderr)
return uuid.uuid4()
d = TestData(__file__)
print(d)
print(get_vars(d))
if USE_SLOTS or PREFER_GETATTR:
print(TestData.__getattr__)
print(TestData._cp_map_)
if USE_SLOTS:
print(d.__slots__)
try:
d.nonexistent = "test"
except AttributeError:
assert USE_SLOTS, "assigned new attribute, but slotting was expected"
print("Confirmed the class is slotted")
else:
assert not USE_SLOTS, "failed to assign new attribute, but slotting was not expected"
print("Confirmed the class is non-slotted")
print(d.unique_id)
print(d.unique_id)
del d.unique_id
print(d.unique_id)
print(d.file_content[:10])
print(d.file_content[:10])
del d.file_content
print(d.file_content[:10])
@mara004

mara004 commented Jul 21, 2026

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment