Home > Software design >  python attrs inherited field value gets overwritten
python attrs inherited field value gets overwritten

Time:01-13

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"))
  • Related