classification
Title: Remove `init` flag from @dataclass?
Type: behavior Stage: resolved
Components: Versions: Python 3.8, Python 3.7
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: eric.smith Nosy List: barry, eric.smith, gvanrossum, ned.deily
Priority: Keywords:

Created on 2018-05-16 14:53 by barry, last changed 2018-05-17 01:12 by eric.smith. This issue is now closed.

Messages (14)
msg316812 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2018-05-16 14:53
In reading over the new dataclasses documentation, I'm unsure what the `init` flag is used for, given that:

* If you already have a __init__(), then dataclasses won't add one
* If you don't have a __init__(), why wouldn't you want dataclasses to add one?  Without that, how do your attributes get initialized?

If there is a valid use case for `init`, I'm happy to add it to the documentation.  If there isn't, can we call YAGNI and remove the flag?

I'm mildly of the same opinion with `repr` but I can see some value in wanting to defer to object.__repr__().  OTOH, it should be pretty easy to just write:

@dataclass
class Foo:
    def __repr__(self):
        return super.__repr__()
msg316816 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2018-05-16 15:10
Make that `return super().__init__()` of course.  I can haz (editable) GitHub issues!
msg316826 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-05-16 16:02
The behavior used to be that we'd raise an error if you tried to overwrite a dunder method. Then we decided that the existence of a dunder method meant you didn't want it overwritten, so now we don't need to explicitly set a flag to False in that case.

However, we did not go back and see which of the flags still make sense. I can't think of a reason why anyone would use the `init` parameter to @dataclass.

It's a little late in the 3.7 release cycle to remove it, so maybe we can decide to deprecate it in 3.8? Although if there's clearly no reason for it to exist, I could be talked in to removing it.

We should probably go through every flag and decide about it individually.
msg316828 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2018-05-16 16:07
On May 16, 2018, at 12:02, Eric V. Smith <report@bugs.python.org> wrote:
> 
> It's a little late in the 3.7 release cycle to remove it, so maybe we can decide to deprecate it in 3.8? Although if there's clearly no reason for it to exist, I could be talked in to removing it.

I think we should get an exemption and just remove it.  There’s no reason to incur such tech-debt right off the bat.

> We should probably go through every flag and decide about it individually.

+1
msg316829 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2018-05-16 16:09
+ned.deily for the 3.7 exemption.
msg316830 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2018-05-16 16:16
If we agree that we don't need it, the time to do it is now, before 3.7.0 releases.
msg316852 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-05-16 19:21
Larry points out that one potential use case is a base class that defines some awesome __init__ that you want to use. Without init=False, there’s no good way to use it.
msg316855 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2018-05-16 19:56
Really?  Why doesn't this work in the derived dataclass?

    def __init__(self, *args, **kws):
        super().__init__(*args, **kws)
msg316859 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-05-16 20:09
I think the concern is:

from dataclasses import *

class B:
    def __init__(self, a, b, c):
        # do something with a, b, c, and maybe use fields(self) to figure out we have a "i" field
        self.i = a + b + c

@dataclass(init=False)
class C(B)
    i: int

c = C(1, 2, 3)

It doesn't seem particularly likely, but do we want to prevent it?
msg316861 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2018-05-16 20:35
On May 16, 2018, at 16:09, Eric V. Smith <report@bugs.python.org> wrote:
> 
> I think the concern is:
> 
> from dataclasses import *
> 
> class B:
>    def __init__(self, a, b, c):
>        # do something with a, b, c, and maybe use fields(self) to figure out we have a "i" field
>        self.i = a + b + c
> 
> @dataclass(init=False)
> class C(B)
>    i: int
> 
> c = C(1, 2, 3)
> 
> It doesn't seem particularly likely, but do we want to prevent it?

What are the intended semantics of this?  I know what happens; c.i == 6

So, if I remove `init=False` and add an explicit __init__(), the same thing still happens.  Is that good enough?

from dataclasses import *

class B:
   def __init__(self, a, b, c):
       # do something with a, b, c, and maybe use fields(self) to figure out we
       # have a "i" field
       self.i = a + b + c

@dataclass
class C(B):
   i: int

   def __init__(self, a, b, c):
       super().__init__(a, b, c)

c = C(1, 2, 3)

(Or maybe it’s the “use fields(self)” bit that you’re pointing out?)
msg316865 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-05-16 20:48
The concern is that you've created an awesome base class, and you've written 500 dataclasses that are derived from it, but you want to use the base class's __init__ for all 500. Do you really want to add a __init__ to each of the 500 classes? We're trying to reduce boilerplate!

Again, I'm not saying it's likely.

The comment about fields(self) is meant to say that they could be inspecting the derived class to figure out which field to set. Something like:

class B:
    def __init__(self, a, b, c):
        first_field = next(iter(fields(self)))
        setattr(self, first_field.name, a+b+c)

mydataclass = dataclass(init=False)

@mydataclass
class C(B):
    i: int

@mydataclass
class D(B):
    j: int

print(C(1, 2, 3))
print(D(4, 5, 6))

produces:
C(i=6)
D(j=15)

That is, the base class could legitimately being doing something with the derived class fields, and you might want to move all of the logic in to a base class.
msg316870 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2018-05-16 21:12
On May 16, 2018, at 16:48, Eric V. Smith <report@bugs.python.org> wrote:

> Do you really want to add a __init__ to each of the 500 classes?

Well, of course *I* do, but I’m weird like that.

> That is, the base class could legitimately being doing something with the derived class fields, and you might want to move all of the logic in to a base class.

Yeah, I can see the points.  I think if these are use cases we want to support, maybe we should describe them in the documentation as justifications for the keyword arguments.
msg316878 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2018-05-17 01:05
I find it a satisfying outcome that we decided *not* to remove this in the last week before rc1. While it's true that dataclasses are a new feature in 3.7.0, considerable effort went into stabilizing the betas.

Let's close this issue.
msg316879 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-05-17 01:12
Okay. I'm sure Ned is relieved!
History
Date User Action Args
2018-05-17 01:12:31eric.smithsetstatus: open -> closed
priority: release blocker ->
type: behavior
messages: + msg316879

resolution: wont fix
stage: resolved
2018-05-17 01:05:24gvanrossumsetmessages: + msg316878
2018-05-16 21:12:55barrysetmessages: + msg316870
2018-05-16 20:48:40eric.smithsetmessages: + msg316865
2018-05-16 20:35:04barrysetmessages: + msg316861
2018-05-16 20:09:21eric.smithsetmessages: + msg316859
2018-05-16 19:56:39barrysetmessages: + msg316855
2018-05-16 19:21:17eric.smithsetmessages: + msg316852
2018-05-16 16:21:52barrysetpriority: normal -> release blocker
2018-05-16 16:16:01ned.deilysetmessages: + msg316830
2018-05-16 16:09:31barrysetnosy: + ned.deily
messages: + msg316829
2018-05-16 16:07:10barrysetmessages: + msg316828
2018-05-16 16:02:42eric.smithsetmessages: + msg316826
2018-05-16 15:10:27barrysetmessages: + msg316816
2018-05-16 14:53:13barrycreate