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
相关产品推荐
相关产品推荐

