Closed JimJJewett closed 6 years ago
I don't think you'd ever call the base class __init__
, although you might call __post_init__
. You wouldn't call the base class __init__
because all of the fields have already been initialized in the derived class's __init__
. I'll see about an example with __post_init__
chaining.
I can't think of a meaningful example of this. The best I can come up with is something like:
@dataclass
class B:
x: int
y: int
# always set x and y to 0, regardless of how this class was initialized
def __post_init__(self):
self.x = 0
self.y = 0
@dataclass
class C(B):
z: int
def __post_init__(self):
B.__post_init__(self)
self.z = 0
assert astuple(C(1, 2, 3)) == (0, 0, 0)
I also don't plan on putting an example in the PEP, since all of this is just normal Python function calls, and not specific to dataclasses. I think this is best left for the user documentation.
That reasoning works for pure-dataclass chains, but not if the dataclass inherits from a regular class, which is explicitly allowed. (Unless the generated init is fancier than I realized.)
So, something like:
class B:
def __init__(self, val):
self.val = val
@dataclass
class C:
i: int
def __post_init__(self):
B.__init__(self, 10*self.i)
c = C(5)
assert c.val == 50
?
I ran into a similar case.
Below fragment (no dataclass
) works:
class B:
def __init__(self, val=1):
self.val = val
class C (B):
pass
c = C()
assert c.val == 1
Making C
into a dataclass doesn't work immediately:
from dataclasses import dataclass
class B:
def __init__(self, val=1):
self.val = val
@dataclass # <-- decorator added
class C (B):
pass
c = C()
assert c.val == 1
This fragment doesn't fail on the assertion itself, but on "'C' objects has no attribute 'val'" (because B.__init__
isn't called).
__post_init__
is required to fix (which I'find somewhat counter-intuitive, as adding the @dataclass
decoration shouldn't require an additional method):
from dataclasses import dataclass
class B:
def __init__(self, val=1):
self.val = val
@dataclass
class C (B):
def __post_init__(self): # <-- method added
super().__init__() # <-- base __init__ explicitly called
c = C()
assert c.val == 1
As @JimJJewett stated, I'd expect adding a call to super().__init__()
(without args) to a dataclass
's generated __init__
would resolve this and leave fewer surprises to the user.
If args are required (i.e. no =1
default in B
's parameter list), an __init__
would also have been needed in the non-dataclass
case, so the user would expect they needed to supply this somehow in the dataclass
case as well.
This repo is now only for the backport of dataclasses.py
to Python 3.6. I'm going to close this issue, please raise it on python-dev if you'd like to continue the discussion.
The reason this is happening is because the undecorated class C
does not have a __init__
, but the decorated one does. There are no fields defined in the decorated class, so nothing gets initialized. The generated __init__
cannot guess what parameters are needed in the base class __init__
, so it never tries to call it. The mechanisms exist to make this work (either @dataclass(init=False)
or __post_init__
), but you do have to understand what's happening.
@ericvsmith is there a way to call super().__init__()
before the dataclass init is called?
One way to call it, would be to have a __pre_init__
as counterpart to __post_init__
.
@boeddeker : please open an issue on bugs.python.org for a feature request. This tracker is just for discussing the backport of dataclasses to previous Python versions.
In particular, show how to ensure that super().init() is called, since (if I understand correctly), it should be called from __post_init__ to ensure that it happens.