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

fcy6dtqo  于 2024-01-05  发布在  Python
关注(0)|答案(1)|浏览(153)

我试着写一个例子,我想在类型检查过程中使用一个冻结的类示例,并将它与一个普通的类交换,以避免支付冻结类的示例化成本。
目标是确保示例在类型检查器中是不可变的,并在运行时使用常规的类。下面是代码片段:

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 = dataclassfrozen = partial(...)的别名。我如何正确地注解它,使类型检查器理解它是一个类示例,而不会抱怨不匹配的参数计数?

  • 附注:这只是一个练习。我知道打开dataclass(frozen=True)要容易得多,在这种情况下我不应该关心性能。我是在阅读Tin Tvrtković关于在编译时冻结attr类示例的blog文章后受到启发而尝试的。*
4jb9z9bj

4jb9z9bj1#

使用@dataclass_transformfrozen_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

相关问题