I have some attrs
classes that inherit from a base class. The parent class has a field that is also an attrs class.
If I instantiate two instances of the child classes and set the common inherited field's sub-field, the other instance's field gets overwritten as well.
I could work around this by using a factory
for the field to specify the required default value with a partial
(method 3).
As it seems, with methods 1 and 2, the instantiation of MyField
for the default value only takes place once, and is somehow shared (?) between the child instances (?)
Is there any other way without this hack?
Here is the code:
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
CodePudding user response:
One solution I found is this: a helper function to create the field with the factory boilerplate:
def my_field(**kwargs):
return attrs.field(factory=lambda: MyField(**kwargs))
and then use this:
@attrs.define
class Parent:
a: MyField = my_field(...)
CodePudding user response:
What's happening here is a special case of the classic:
from attrs import define
@define
class C:
a: list = []
Unless you use factories, there's only one instance of MyField
that is used by all instances of C and their subclasses.
As you've found out, if you want a separate MyField
per instance, you have to create every time.
I personally find the following approach more idiomatic:
@attrs.define
class MyField:
name: str
value: int = 0
@attrs.define
class Parent:
a: MyField = attrs.Factory(lambda: MyField(name="default_name"))