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.
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']