【问题标题】:How to make non-frozen dataclass frozen, and vice versa?如何使非冻结数据类冻结,反之亦然?
【发布时间】:2019-05-10 00:53:32
【问题描述】:

我想知道使以下数据类bar 冻结的任何简单方法。

@dataclass
class Bar:
    foo: int
bar = Bar(foo=1)

也就是说,我想要下面的函数some_fn_to_freeze

frozen_bar = some_fn_to_freeze(bar)
frozen_bar.foo = 2 # Error

还有,反函数some_fn_to_unfreeze

bar = som_fn_to_unfrozen(frozen_bar)
bar.foo = 3 # not Error

【问题讨论】:

    标签: python python-dataclasses


    【解决方案1】:

    Python 数据类很棒,但如果您能够使用第三方库,attrs 包是更灵活的选择。例如:

    import attr
    
    # Your class of interest.
    @attr.s()
    class Bar(object):
       val = attr.ib()
    
    # A frozen variant of it.
    @attr.s(frozen = True)
    class FrozenBar(Bar):
       pass
    
    # Three instances:
    # - Bar.
    # - FrozenBar based on that Bar.
    # - Bar based on that FrozenBar.
    b1 = Bar(123)
    fb = FrozenBar(**attr.asdict(b1))
    b2 = Bar(**attr.asdict(fb))
    
    # We can modify the Bar instances.
    b1.val = 777
    b2.val = 888
    
    # Check current vals.
    for x in (b1, fb, b2):
        print(x)
    
    # But we cannot modify the FrozenBar instance.
    try:
        fb.val = 999
    except attr.exceptions.FrozenInstanceError:
        print(fb, 'unchanged')
    

    输出:

    Bar(val=888)
    FrozenBar(val=123)
    Bar(val=999)
    FrozenBar(val=123) unchanged
    

    【讨论】:

    • attrs 可能更灵活,但这里没有什么是 dataclass 做不到的。例如,继承也适用于dataclass;我只是发现有单独的课程更清楚。 (另外,asdict 很危险,因为递归和复制行为令人困惑。)
    • @user2357112 冻结的数据类不能从非冻结的数据类继承,反之亦然,除非我刚刚运行的实验存在缺陷。
    • 嗯。看起来你是对的。如果父项没有字段,则似乎不会应用检查,这导致我的第一次测试失败。
    • @FMc 很酷!你觉得用attrs实现树形结构怎么样?
    • @Yohei 听起来不错。请记住,attrs 生成的类是常规 Python 类。
    【解决方案2】:

    dataclass 没有内置支持。 Frozen-ness 是在类范围的基础上跟踪的,而不是每个实例,并且不支持自动生成数据类的冻结或解冻等效项。

    虽然您可以尝试做一些事情来动态生成新的数据类,但它与isinstance== 以及您想要工作的其他事情的交互非常糟糕。只写两个数据类和转换器方法可能更安全:

    @dataclass
    class Bar:
        foo: int
        def as_frozen(self):
            return FrozenBar(self.foo)
    
    @dataclass(frozen=True)
    class FrozenBar:
        foo: int
        def as_unfrozen(self):
            return Bar(self.foo)
    

    【讨论】:

      【解决方案3】:

      改变frozen 数据类的标准方法是使用dataclasses.replace

      old_bar = Bar(foo=123)
      new_bar = dataclasses.replace(old_bar, foo=456)
      assert new_bar.foo == 456
      

      对于更复杂的用例,您可以使用来自:https://github.com/google/etils 的 dataclass utils 模块

      它添加了一个my_dataclass = my_dataclass.unfrozen() 成员,允许直接改变frozen 数据类

      # pip install etils[edc]
      from etils import edc
      
      @edc.dataclass(allow_unfrozen=True)  # Add the `unfrozen()`/`frozen` method
      @dataclasses.dataclass(frozen=True)
      class A:
        x: Any = None
        y: Any = None
      
      
      old_a = A(x=A(x=A()))
      
      # After a is unfrozen, the updates on nested attributes will be propagated
      # to the top-level parent.
      a = old_a.unfrozen()
      a.x.x.x = 123
      a.x.y = 'abc'
      a = a.frozen()  # `frozen()` recursively call `dataclasses.replace`
      
      # Only the `unfrozen` object is mutated. Not the original one.
      assert a == A(x=A(x=A(x = 123), y='abc'))
      assert old_a == A(x=A(x=A()))
      

      如示例中所示,您可以返回数据类的 unfrozen/frozen 副本,该数据类明确设计用于改变嵌套数据类。

      @edc.dataclass 还在数据类中添加了一个a.replace(**kwargs) 方法(dataclasses.dataclass 的别名)

      a = A()
      a = a.replace(x=123, y=456)
      assert a == A(x=123, y=456)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-19
        • 1970-01-01
        • 2020-11-26
        相关资源
        最近更新 更多