使用已缓存的属性在一个命名元组上。

huangapple go评论72阅读模式
英文:

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&#39;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&#39;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(&quot;Fresh compute of area&quot;)
        return self.x * self.y


r = Rectangle(2, 4)

r.area
Fresh compute of area
8

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

# can&#39;t add new attributes
r.z = &#39;thing&#39;
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
  File &quot;&lt;string&gt;&quot;, line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field &#39;z&#39;

# can&#39;t reassign existing attributes
r.x = 4
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
  File &quot;&lt;string&gt;&quot;, line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field &#39;x&#39;

# 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>



huangapple
  • 本文由 发表于 2023年7月11日 04:02:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76656973.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定