In general, it's not possible to know how to call
super.__init__() if you don't a priori know the arguments it
takes. That's why dataclasses doesn't guess. The only
general-purpose way to do it is to use *args and **kwargs.
I was about to make this same point, thanks. super.__init__() absolutely should NOT be called by default in an auto-generated __init__.
However, dataclasses, could, optionally, take *args and **kwargs, and store them in a instance attribute. Then you could call super(), or anything else, in __post_init__. And there are other reasons to want them to take arbitrary other parameters (like to ignore them, see the PR).
Since a
goal of dataclasses is to use good typing information that works
with type checkers, that seems to defeat part of the purpose.
I have never used a type checker :-) -- but I still really dislike the passing around of *args, **kwargs -- it makes your API completely non-self documented. So I'll agree here.
One thing you could do in this scenario is to use a classmethod
as an "alternate constructor". This basically gives you any post-
and pre- init functionality you want, using any regular parameters
or *args and **kwargs as fits your case.
That's a nice way to go -- I suggested a similar way by adding another layer of subclassing. Using a classmethod is cleaner, but using another layer of subclassing allows you to keep the regular __init__ syntax -- and if you want to make these classes for others to use, the familiar API is a good thing.
I do wonder if we could have a __pre_init__ -- as a way to add something like this classmethod, but as a regular __init__. Butnow that I think about it, here's another option: if the user passes in init=False, (or maybe some other value), then the __init__ could still be generated, and accesable to the user's __init__. It might look something like this (to follow your example):
from dataclasses import dataclass
class BaseClass:
def __init__(self, x, y):
print('initializing base class', x, y)
self.x = x
self.y = y
@dataclass(init=False)
class MyClass(BaseClass):
a: int
b: int
c: int
def __init__(self, a, b, c, x, y):
super.__init__(x, y)
# _ds__init__ would be the auto-generated __init__
self._ds__init__(self, a, b, c)
return self
c = MyClass.new(1, 2, 3, 10, 20)
print(c)
print(c.x, c.y)
> To Brett's point: has anyone looked to see what attrs does here?
When last I looked, they didn't call any super __init__, but maybe
they've added something to address this.
"Please note that attrs does not call super() *ever*." (emphasis theirs)
So that's that. :-)
"Embrace classmethods as a filter between reality and what’s best for you to work with"
and:
"Generally speaking, the moment you think that you need finer control over how your class is instantiated than what attrs
offers, it’s usually best to use a classmethod factory..."
Which seems to support your classmethod idea :-)
But I still think that it would be good to have some kin dof way to customise the __init__ without re-writting the whole thing, and/or have a way to keep *args,**kwargs around to use in __post_init__
Maybe a gitHub repo just for dataclasses?
@Eric V. Smith: what do you
think? Is there a way to keep them moving forward?
I think it's fine to make suggestions and have discussions here.
Dataclasses aren't sufficiently large that they need their own
repo (like tulip did, for example).
Sure -- and go to know you're monitoring this list (and no, I don't expect instant responses) But it's just that I've seen at least one idea (the **kwargs one) kind of wither and die, rather than be rejected (at least not obviously) -- which maybe would have happened anyway.
But I also don't think we're going to just add lots of features
to dataclasses. They're meant to be lean.
As they should be.
I realize drawing the
line is difficult. For example, I think asdict and astuple were
mistakes, and should have been done outside of the stdlib.
I agree there -- I've found I needed to re-implement asdict anyway, as I needed something a little special. But that's only possible because dataclasses already have the infrastructure in place to do that.
So I think any enhancements should be to allow third-party extensions, rather than actual new functionality. In this case, yes, folks can use an alternate constructor, but there is no way to get other arguments passed through an auto-generated__init__ -- so there are various ways that one cannot extend dataclasses -- I'd like to see that additional feature, to enable various third-party extensions.
-CHB