Closed Wenzel closed 3 months ago
I can't quite follow everything that's happening because you're essentially breaking attrs with you super()
calls. attrs never calls super() and is not built for it. Every class builds an optimal version of __init__
according the fields that it discovers.
Hi @hynek and thanks for your answer.
So a class decorated by attrs is not meant to call super()
?
How should it initialize it's parent attributes ?
In the docs you mentioned that it's __attrs_pre_init__()
role:
https://www.attrs.org/en/stable/init.html#pre-init
Which is what I did.
So i suppose it's my super()
call in __attrs_post_init__()
that you think is breaking attrs ?
Because even if I comment that super() call:
@define(slots=False, auto_attribs=True, auto_detect=True)
class B(A):
field_3: int = 0
def __attrs_pre_init__(self, field_1: int, *args, **kwargs):
super().__init__(field_1)
def __attrs_post_init__(self):
# super().__attrs_post_init__()
# raises AttributeError when accessing field_3
print(f"B.__attrs_post_init__: field_3 default = {self.field_3}")
print(self.field_3)
The self.field_3
attribute will not be initialized by the default value at this point.
How attrs is expected to behave with inheritance ? Is that part of the scope ? Or should I only use attrs with standalone without inheritance ?
Thanks !
So a class decorated by attrs is not meant to call
super()
? How should it initialize it's parent attributes ?
You don't have to. The __init__
of an attrs class that inherits from another attrs class will initialize them for you.
In the docs you mentioned that it's
__attrs_pre_init__()
role: https://www.attrs.org/en/stable/init.html#pre-init
It says:
The sole reason for the existence of attrs_pre_init is to give users the chance to call super().init(), because some subclassing-based APIs require that.
I guess I can add a clarification that that's never necessary with attrs classes.
Which is what I did. So i suppose it's my
super()
call in__attrs_post_init__()
that you think is breaking attrs ? Because even if I comment that super() call:@define(slots=False, auto_attribs=True, auto_detect=True) class B(A): field_3: int = 0 def __attrs_pre_init__(self, field_1: int, *args, **kwargs): super().__init__(field_1) def __attrs_post_init__(self): # super().__attrs_post_init__() # raises AttributeError when accessing field_3 print(f"B.__attrs_post_init__: field_3 default = {self.field_3}") print(self.field_3)
The
self.field_3
attribute will not be initialized by the default value at this point.
This works as expected:
from abc import ABC
from attrs import define, field
@define
class A(ABC):
field_1: int = 42
field_2: int = field(init=False)
def __attrs_post_init__(self):
print(f"A.__attrs_post_init__: field_1 default = {self.field_1}")
self.field_2 = self.field_1 * 2
@define
class B(A):
field_3: int = 0
def __attrs_post_init__(self):
super().__attrs_post_init__()
print(f"B.__attrs_post_init__: field_3 default = {self.field_3}")
print(self.field_2)
print(self.field_3)
if __name__ == '__main__':
instance = B()
How attrs is expected to behave with inheritance ? Is that part of the scope ? Or should I only use attrs with standalone without inheritance ?
attrs does work with inheritance in general, but it can get a bit hairy when you use a lot of pre-inits and post-inits.
JFTR, you can always inspect the __init__
that attrs wrote for you:
>>> import inspect
>>> print(inspect.getsource(B.__init__))
def __init__(self, field_1=attr_dict['field_1'].default, field_3=attr_dict['field_3'].default):
self.field_1 = field_1
self.field_3 = field_3
self.__attrs_post_init__()
Hi,
I'm surprised that this
attrs
code doesn't work:When I try to access
field_3
in the child's__attrs_post_init__
, the attribute, despite having a default value like another one in the parent class, is undefined.My assumption was that, by the time
attrs
has reached__attrs_post_init__
, the fields with default values should have been set already (which is the case for the parent class)Is there some subtlety that i'm missing here with attrs initialization ?
Thanks !