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:
x, y, area = rect
should not be possible)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