You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python attrs继承类字段值被覆盖问题及替代解法咨询

问题描述

我有多个继承自基类的attrs类,父类包含一个类型为attrs类的字段。实例化两个子类对象后,修改该继承字段的子属性时,另一个实例的对应字段会被意外覆盖。目前通过factory结合partial的方式(方法3)规避了问题,但发现方法1和2中MyField的默认实例仅被创建一次并在子类实例间共享。想知道是否存在无需此类取巧方式的其他解决办法?

相关代码:

import functools
import attrs


def test_attrs_problem():

    @attrs.define
    class MyField:
        name: str
        value: int = 0

    @attrs.define
    class Parent:
        # Test fails with the following two field definitions:
        #  --- method 1 ---
        # a: MyField = MyField(name="default_name")
        #  --- method 2 ---
        # a: MyField = attrs.field(default=MyField(name="default_name"))

        # Test passes with the following two field definitions:
        #  --- method 3 ---
        a: MyField = attrs.field(
            factory=functools.partial(
                MyField,
                name="default_name"
            )
        )

    @attrs.define
    class Child1(Parent):
        b: int = 42

    @attrs.define
    class Child2(Parent):
        c: int = 43

    # creating an instance of the Child1 class
    c1 = Child1()
    c1.a.value = 1
    before = c1.a.value
    print("before", c1)

    # creating a instance of the Child2 class
    c2 = Child2()
    # setting the common inherited field's value field for the c2 instance
    c2.a.value = 2
    after = c1.a.value
    print("after", c1)

    # expecting that the value in the c1 instance is the same
    assert after == before

解决方案

核心原因

方法1和2失效的本质是Python类级别的默认值只会在类定义时初始化一次,所有实例共享同一个MyField对象;而factory参数会在每次实例化类时调用指定函数生成新对象,因此能保证每个实例拥有独立的字段实例。

以下是两种正规的替代方案:

1. 使用Lambda作为Factory(更简洁的写法)

直接用lambda表达式替代functools.partial,作为factory参数的取值,这是attrs官方推荐的生成动态默认值的方式:

@attrs.define
class Parent:
    a: MyField = attrs.field(factory=lambda: MyField(name="default_name"))

2. 利用__attrs_post_init__钩子初始化

将字段默认值设为None,然后在实例化后的钩子方法中创建独立的MyField实例:

@attrs.define
class Parent:
    a: MyField = None

    def __attrs_post_init__(self):
        # 仅当未传入a参数时,创建默认实例
        if self.a is None:
            self.a = MyField(name="default_name")

说明

这两种方案都属于attrs的标准用法,并非“取巧”。它们的核心逻辑都是确保每个类实例在创建时生成独立的MyField对象,从根源上避免了实例间共享可变对象导致的状态污染问题。

内容的提问来源于stack exchange,提问作者waszil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 02:01:04