2
from typing import NamedTuple
from functools import cached_property

class Rectangle(NamedTuple):
    x: int
    y: int

    @cached_property
    def area(self):
        return self.x * self.y

I thought this class definition would complain something about the __slots__ on Rectangle, but apparently the class definition is valid. It doesn't fail until too late, if/when the getter is actually accessed:

>>> rect = Rectangle(2, 3)
>>> rect.area
...
TypeError: Cannot use cached_property instance without calling __set_name__ on it.
>>> Rectangle.

Well, that's weird, but okay..

>>> Rectangle.area.__set_name__(Rectangle, "area")
>>> rect.area
...
TypeError: No '__dict__' attribute on 'Rectangle' instance to cache 'area' property.

Is there a better recipe for cached properties on named tuples? Requirements:

  • It should not appear to be a real field (x, y, area = rect should not be possible)
  • It should be lazy (not eagerly computed) and cached (not recomputed every time accessed)
  • Wherever the storage is should not leak memory (it should be deleted when the tuple instance itself is deleted)

1 Answer 1

2

You probably want a cached_property on a dataclass with the frozen=True setting instead. The frozen setting allows the dataclass to function like an immutable NamedTuple:

from dataclasses import dataclass
from functools import cached_property

@dataclass(frozen=True)
class Rectangle:
    x: int
    y: int
    
    @cached_property
    def area(self):
        print("Fresh compute of area")
        return self.x * self.y


r = Rectangle(2, 4)

r.area
Fresh compute of area
8

# this is cached, so print doesn't run on a second
# call to area
r.area
8

# can't add new attributes
r.z = 'thing'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'z'

# can't reassign existing attributes
r.x = 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'x'

# You get __str__ for free
print(r)
Rectangle(x=2, y=4)

# as well as __hash__
hash(r)
3516302870623680066

# and __eq__
r == Rectangle(2, 4)
True

r == Rectangle(1, 4)
False

Not the answer you're looking for? Browse other questions tagged or ask your own question.