0

I'm new with attrs module & I've encountered something that I didn't understand very well. For simplicity I have this code below:

import attr
from typing import List

@attr.define
class A:
    a: str = attr.ib(init=False, default='hi')
    b: str = attr.ib(init=False, default='bye')
    ab: List[str] = attr.ib(init=False, default=[a, b])


def main() -> None:
    a = A()
    print(a.a)
    print(a.b)
    print(a.ab)


if __name__ == '__main__':
    main()

But while I expected to get the output:

hi
bye
["hi", "bye"]

As a beginner, The output I get is a bit strange to me:

hi
bye
[_CountingAttr(counter=18, _default='hi', repr=True, eq=True, order=True, hash=None, init=False, on_setattr=None, metadata={}), _CountingAttr(counter=19, _default='bye', repr=True, eq=True, order=True, hash=None, init=False, on_setattr=None, metadata={})]

I'll appreciate any explanation to understand the reason & how I can handle it.

2
  • Inside the class scope, a and b are literally attr.ib placeholders, not their defaults. So [a, b] is a list of the placeholders; attrs doesn't know that you expect it to peek arbitrarily deep into the default and pick out placeholders there. Commented Feb 17, 2022 at 15:57
  • @MisterMiyagi is there a way to access the default value manually from inside the class? Commented Feb 17, 2022 at 17:05

2 Answers 2

5

At class definition time. a and b are just the result of attr.ib(...). And if somehow that worked, default=[a, b] means to give this reference to every object that this class creates. Meaning that every A you create would reference the same list.

What you want to do it's to use attr.ib(factory=list) when you want a new list for every object. factory=list it's just an alias to default=attr.Factory(list). attr.Factory has this keyword takes_self that can enhance this functionality further. So you can use attr.ib(default=attr.Factory(lambda self: [self.a, self.b], takes_self=True)) to do what you want.

n [2]: import attr
   ...: from typing import List
   ...: 
   ...: @attr.define
   ...: class A:
   ...:     a: str = attr.ib(init=False, default='hi')
   ...:     b: str = attr.ib(init=False, default='bye')
   ...:     ab: List[str] = attr.ib(default=attr.Factory(lambda self: [self.a, self.b], takes_self=True))
   ...: 
   ...: def main() -> None:
   ...:     a = A()
   ...:     print(a.a)
   ...:     print(a.b)
   ...:     print(a.ab)
   ...: 
   ...: 
   ...: if __name__ == '__main__':
   ...:     main()
   ...: 
hi
bye
['hi', 'bye']

If you need to do something more complex or dislike lambdas, you can also use:

In [3]: import attr
   ...: from typing import List
   ...: 
   ...: @attr.define
   ...: class A:
   ...:     a: str = attr.ib(init=False, default='hi')
   ...:     b: str = attr.ib(init=False, default='bye')
   ...:     ab: List[str] = attr.ib()
   ...: 
   ...:     @ab.default
   ...:     def _(self):
   ...:         return [self.a, self.b]
   ...: 
   ...: def main() -> None:
   ...:     a = A()
   ...:     print(a.a)
   ...:     print(a.b)
   ...:     print(a.ab)
   ...: 
   ...: 
   ...: if __name__ == '__main__':
   ...:     main()
   ...: 
hi
bye
['hi', 'bye']
Sign up to request clarification or add additional context in comments.

Comments

0

I have found the solution in attrs.org

attrs module has a post __init__ magic function: __attrs_post_init__ exactly for cases like mine that I described in my question.

That's the solution, post it here for others who will encounter the same question like me:

import attr
from typing import List

@attr.define
class A:
    a: str = attr.ib(init=False, default='hi')
    b: str = attr.ib(init=False, default='bye')
    ab: List[str] = attr.ib(default=[])

    def __attrs_post_init__(self):
        self.ab = [self.a, self.b]


def main() -> None:
    a = A()
    print(a.a)
    print(a.b)
    print(a.ab)


if __name__ == '__main__':
    main()

1 Comment

That works, but it sets the ab value twice and discards the first value, which is unnecessary. Also, it's not recommended to use mutable defaults.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.