英文:
Using a cached property on a named tuple
问题
from typing import NamedTuple
from functools import cached_property
# 定义一个名为Rectangle的命名元组类
class Rectangle(NamedTuple):
x: int
y: int
# 使用cached_property装饰器定义一个计算面积的属性
@cached_property
def area(self):
return self.x * self.y
我认为这个类定义会对Rectangle上的__slots__
属性发出一些警告,但显然这个类定义是有效的。直到实际访问getter时才会失败:
>>> rect = Rectangle(2, 3)
>>> rect.area
...
TypeError: Cannot use cached_property instance without calling __set_name__ on it.
嗯,这很奇怪,但没关系。
>>> Rectangle.area.__set_name__(Rectangle, "area")
>>> rect.area
...
TypeError: No '__dict__' attribute on 'Rectangle' instance to cache 'area' property.
是否有更好的方法来在命名元组上使用缓存属性?要求如下:
- 它不应该看起来像一个真正的字段(
x, y, area = rect
不应该可行) - 它应该是延迟计算的(不是急切计算),并且缓存(不会每次访问都重新计算)
- 存储位置不应该泄漏内存(应该在删除元组实例本身时删除)
<details>
<summary>英文:</summary>
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)
</details>
# 答案1
**得分**: 1
你可能想要在具有`frozen=True`设置的`dataclass`上使用`cached_property`。冻结设置允许`dataclass`的行为类似于不可变的`NamedTuple`:
```python
from dataclasses import dataclass
from functools import cached_property
@dataclass(frozen=True)
class Rectangle:
x: int
y: int
@cached_property
def area(self):
print("计算面积")
return self.x * self.y
r = Rectangle(2, 4)
r.area
计算面积
8
# 这是缓存的,所以第二次调用area时不会运行print
r.area
8
# 无法添加新属性
r.z = 'thing'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: 不能分配给字段 'z'
# 无法重新分配现有属性
r.x = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: 不能分配给字段 'x'
# 你会自动获得__str__
print(r)
Rectangle(x=2, y=4)
# 以及__hash__
hash(r)
3516302870623680066
# 和__eq__
r == Rectangle(2, 4)
True
r == Rectangle(1, 4)
False
英文:
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
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论