如何在Python中静态强制冻结数据类?

问题描述 投票:0回答:1

我正在尝试编写一个示例,其中我想在类型检查期间使用冻结数据类实例并将其与普通数据类交换,以避免支付冻结数据类的实例化成本。

目标是确保实例在类型检查器中是不可变的,并在运行时使用常规数据类。这是片段:

from dataclasses import dataclass
from typing import TYPE_CHECKING
from functools import partial

if TYPE_CHECKING:
    frozen = partial(dataclass, frozen=True)
else:
    frozen = dataclass

@frozen
class Foo:
    x: int
    y: int

foo = Foo(1, 2) # mypy complains about the number of arguments

foo.x = 3 # instead, mypy should complain here

这在运行时按预期工作,但运行 mypy 会引发此错误。 Pyright 也给了我同样的错误:

foo.py:49: error: Too many arguments for "Foo"  [call-arg]

在此片段中,类型检查器可以捕获突变错误:

@dataclass(frozen=True)
class Foo:
    x: int
    y: int

foo = Foo(1, 2)

foo.x = 3 # mypy correctly catches the error here

所以,我猜测类型检查器不喜欢我使用别名

frozen = dataclass
frozen = partial(...)
。如何正确注释它,以便类型检查器了解它是一个数据类实例,并且不会抱怨参数计数不匹配?

P.S:这只是一个练习。我知道打开

dataclass(frozen=True)
更容易,而且在这种情况下我不应该关心性能。在阅读了 Tin Tvrtković 的关于使 attr 类实例在编译时冻结的博客帖子后,我受到启发尝试此操作。

python mypy type-hinting python-typing python-dataclasses
1个回答
2
投票

@dataclass_transform
frozen_default = True
一起使用:

if TYPE_CHECKING:
    T = TypeVar('T')
    
    @dataclass_transform(frozen_default = True)
    def frozen(cls: type[T]) -> type[T]:
        ...
else:
    frozen = dataclass

frozen_default
是在 Python 3.12 中添加的。然而,由于
@dataclass_transform
故意接受所有关键字参数,所以 3.11 (完全相同)和 lower (使用
typing_extensions
)就可以了。

这适用于 mypypyright

reveal_type(Foo)  # mypy    => (x: int, y: int) -> Foo
                  # pyright => type[Foo]

foo = Foo(1, 2)   # mypy + pyright => fine

foo.x = 3         # mypy    => error: Property "x" defined in "Foo" is read-only
                  # pyright => error: Cannot assign member "x" for type "Foo"; "Foo" is frozen
© www.soinside.com 2019 - 2024. All rights reserved.