【问题标题】:Dataclass-style object with mutable and immutable properties?具有可变和不可变属性的数据类样式对象?
【发布时间】:2020-02-20 06:19:21
【问题描述】:

我一直在使用从文件中动态加载属性名称的数据类,但我无法找到创建“冻结”和“非冻结”属性的方法。我相信数据类只允许您将所有属性设置为冻结或非冻结。

到目前为止,我创建了一个冻结的数据类并添加了一个可变类作为我可以随时更改的属性之一,但我对这种方法的可读性不太满意。

是否有另一个 Python 数据类人们会推荐,而无需实现能够设置可变/不可变属性的类?

import dataclasses

class ModifiableConfig:
    """There is stuff in here but you get the picture."""
    ...

config_dataclass = dataclasses.make_dataclass(
    'c',
    [(x, type(x), v) for x, v in config.items()] + [('var', object, ModifiableConfig())],
    frozen=True
)

但是,我希望能够选择冻结哪些属性,哪些不冻结。不再需要向数据类添加额外的类。它可能看起来像这样:

config_dataclass_modifiable = dataclasses.make_dataclass(
            'c', [(x, type(x), v, True if 'modifiable' in x else False) for x, v in config.items()])

请注意“True if 'modifiable' in x else False”,我并不是说我最终会这样做,但希望这有助于更好地理解我的问题。

【问题讨论】:

  • IMO 如果您按照您所拥有的内容(可能带有一些示例用法)添加一个您认为不太可读的示例,这将非常有帮助。这将使我们更好地了解您在说什么。

标签: python immutability python-3.7 mutable python-dataclasses


【解决方案1】:

调整属性处理的常规方法是编写自定义__setattr__ 方法,该方法允许您覆盖属性分配的默认行为。不幸的是,该方法也是数据类挂钩以强制执行frozen 逻辑的方法,该逻辑通过在您尝试触摸它时立即抛出TypeError: Cannot overwrite attribute __setattr__ in class ModifiableConfig 来有效地锁定函数以防止进一步更改。

因此,我认为没有直接且简单的解决方案可以解决您的问题。在我看来,您将类的可变部分委托给内部对象或字典的方法一点也不差或不符合 Python 风格,但是如果您可以从需求列表中删除 frozen 并且只想要一个部分可变数据类,您可以尝试在此处使用此 bootleg-semi-frozen 配方,该配方使用标志 semi 更新 dataclass 装饰器,您可以打开该标志以获得您描述的行为:

from dataclasses import dataclass as dc
from traceback import format_stack

def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False,
              unsafe_hash=False, frozen=False, semi=False):

    def wrap(cls):
        # sanity checks for new kw
        if semi:
            if frozen:
                raise AttributeError("Either semi or frozen, not both.")
            if cls.__setattr__ != cls.mro()[1].__setattr__:
                raise AttributeError("No touching setattr when using semi!")

        # run original dataclass decorator
        dc(cls, init=init, repr=repr, eq=eq, order=order,
           unsafe_hash=unsafe_hash, frozen=frozen)

        # add semi-frozen logic
        if semi:
            def __setattr__(self, key, value):
                if key in self.__slots__:
                    caller = format_stack()[-2].rsplit('in ', 1)[1].strip()
                    if caller != '__init__':
                        raise TypeError(f"Attribute '{key}' is immutable!")
                object.__setattr__(self, key, value)
            cls.__setattr__ = __setattr__

        return cls

    # Handle being called with or without parens
    if _cls is None:
        return wrap
    return wrap(_cls)

我在这里很简短,并没有在这里解决一些潜在的极端情况。有更好的方法来处理包装,以便内部更加一致,但它会使这个已经很复杂的 sn-p 更加复杂。

鉴于这个新的dataclass 装饰器,您可以像这样使用它来定义具有一些不可变属性和一些可变属性的数据类:

>>> @dataclass(semi=True)
... class Foo:
...     # put immutable attributes and __dict__ into slots 
...     __slots__ = ('__dict__', 'x', 'y')
...     x: int
...     y: int
...     z: int
...
>>> f = Foo(1, 2, 3)
>>> f        # prints Foo(x=1, y=2, z=3)
>>> f.z = 4  # will work
>>> f.x = 4  # raises TypeError: attribute 'x' is immutable!

您不必使用__slots__ 将可变部分与不可变部分分开,但出于几个原因(例如作为不属于默认数据类的元属性repr ) 并且对我来说很直观。

【讨论】:

  • 感谢您的回复!!我会试一试!
  • 我希望它有用 =) 如果某些事情没有按预期工作,请随时返回。
【解决方案2】:

在上面的最佳答案中,如果Foo 是另一个类的子类,则代码会中断。为了解决这个问题,行:

super(type(self), self).__setattr__(key, value)

应改为:

super(type(cls), cls).__setattr__(key, value)

这样,super 实际上是向上遍历而不是进入无限的自引用。

【讨论】:

  • 这不是那么简单,您的解决方案在TypeError: can't apply this __setattr__ to type object 的基本情况下中断。在没有检查的情况下更新类而不是实例上的属性也很奇怪,我假设结果更改将在 all 实例上可见。我会看看我是否可以修复该 setattr 中的 mro 以便它与继承一起使用,但它可能是一个干净的解决方案是不可能的 - 将继承与类装饰器混合是非常可疑的。
  • 感谢您发现错误顺便说一句,我更新了我的答案以使用 object.__setattr__ 现在,这将始终有效,除非某些父类定义了应该使用的自定义 __setattr__
  • 是的,你是对的。这样解决方案更干净!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多