如何在 python 数据类中实现变量间依赖的封装

How to achieve encapsulation in python dataclasses with dependencies between variables

我最近了解到数据classes 在编写主要用作数据容器的 classes 时减少样板代码的好处。

我希望能够使用数据classes,但希望使用 private/protected 变量来保持我已经习惯的常规 classes 的封装级别and/or 个属性。

我希望能够封装两个字段,以便它们只根据某些预期规则一起更新。

举个最简单的例子:

class EncapsulatedClass:
    def __init__(self, field1, field2):
        validate_args(field1, field2)
        self._field1 = field1
        self._field2 = field2

    def update_fields(self, arg1, arg2):
        if arg1:
            self._field1 += 1
        if arg2:
            self._field2 += 1
        if arg1 and arg2:
            self._field1 = 0

我喜欢这种行为的原因是它保证(直到用户修改受保护的参数)field1 和 field2 有一些预期的关系,这可以在初始化时验证,然后永远不需要再次验证。

但是,对于标准 class,我需要实施 __eq__ __repr____init__,我宁愿避免减少样板文件。

有没有一种方法可以使用数据classes 和最少的样板来实现这种行为?

一般来说,不会。但具体取决于您要执行的操作,可能会有各种特定的解决方法。例如,在这里我可能会创建一个对元组进行操作的 属性。

class EncapsulatedClass:
    def __init__(self, field1, field2):
        self.field = (field1, field2)

    @property
    def field(self):
        return (self._field1, self._field2)

    @field.setter
    def field(self, value):
        v1, v2 = value  # Here, just letting a possible exception propogate
        if v1:
            self._field1 += 1
        if v2:
            self._field2 += 1
        if v1 and v2:
            self._field1 = 0

    # Now it's a wrapper around the setter
    def update_fields(self, field1, field2):
        self.field = (field1, field2)

您可以继续在数据类中使用 属性。可以定义 __post_init__ 来初始化 属性,而不必重新实现将使用底层属性的所有内容。

@dataclass
class EncapsulatedClass:
    _field1: int
    _field2: int

    def __post_init__(self):
        self.field = (self._field1, self._field2)
        
    @property
    def field(self):
        return (self._field1, self._field2)

    @field.setter
    def field(self, value):
        v1, v2 = value  # Here, just letting a possible exception propogate
        if v1:
            self._field1 += 1
        if v2:
            self._field2 += 1
        if v1 and v2:
            self._field1 = 0

    def update_fields(self, field1, field2):
        self.field = (field1, field2)