将辅助变量传递给数据类 __init__ 而不分配它们

Passing helper variables to dataclass __init__ without assigning them

我有一个根级别 config class,我通过依赖注入在我的代码库中传递它。

问题是,我有这个数据class需要这个配置中的一些属性来计算__post_init__()中的值world_coords

为了保持我的测试干净并规避其他测试导入问题(此处未讨论),我希望能够将配置直接传递给该对象,而不是从导入中读取这些值。但是,如果我将配置结构化为一个参数,它就会成为数据 class 的一个属性,这是我试图避免的。 RoadSegmentNode 确实不需要在使用后保留对配置的引用。

这是class的基本结构:

@dataclass(eq=True, frozen=True)  # make hashable
class RoadSegmentNode:

    tile_index: Tuple[int, int]  # (r, c)
    dir: Direction
    node_type: RoadNodeType
    world_coords: Tuple[int, int] = field(init=False)

    def __post_init__(self):

        # (Use config variables here, e.g. `config.TILE_WIDTH`, to calculate x and y)

        # Hack to get around frozen=True. We don't care that we're mutating
        # an "immutable" object on __init__().
        object.__setattr__(self, "world_coords", (x, y))

这是我暂时使用依赖注入模型来解锁测试的权宜之计。请注意 RoadSegmentNode 现在如何拥有一堆仅用于初始化的新属性。这比保留对配置的引用要好一点,因为至少它们是明确的,但它仍然是一个非常糟糕的设计。

@dataclass(eq=True, frozen=True)  # make hashable
class RoadSegmentNode:

    # NOTE: DO NOT ACCESS THESE ATTRIBUTES!
    grid_width: int
    grid_height: int
    tile_width: int
    tile_height: int
    road_width: int

    tile_index: Tuple[int, int]  # (r, c)
    dir: Direction
    node_type: RoadNodeType
    world_coords: Tuple[int, int] = field(init=False)

    def __post_init__(self):

        # (Use attributes here, e.g. `self.tile_width`, to calculate x and y)

        # Hack to get around frozen=True. We don't care that we're mutating
        # an "immutable" object on __init__().
        object.__setattr__(self, "world_coords", (x, y))

如何将配置传递给数据class进行初始化而不使其成为数据class的属性?我什至应该考虑为这个用例使用 dataclass 吗?我相信初衷是保持所有实例不可变,但我无法确认。

您应该将 config 定义为 init-only variable。这样它将传递给 __post_init__(),但之后会消失:

from dataclass import dataclass, field
from typing import InitVar

@dataclass(eq=True, frozen=True)
class RoadSegmentNode:

    tile_index: Tuple[int, int]
    dir: Direction
    node_type: RoadNodeType
    world_coords: Tuple[int, int] = field(init=False)
    config: InitVar[Config]  # will not appear in instances


    def __post_init__(self, config):
        x, y = ..., ...  # calculate using config

        # looks hacky for sure, but is the right way to work with frozen dataclasses
        object.__setattr__(self, "world_coords", (x, y))