Skip to content

dataclass tries to generate __init__() even though the class already defines one #148941

Description

@tonyyuyiding

Bug report

Bug description:

When running this code:

from dataclasses import dataclass

@dataclass
class A:
    a: int = 1

@dataclass
class B(A):
    b: int
    def __init__(self, *args, **kwargs): ...

I got TypeError: non-default argument 'b' follows default argument

However, if I set init=False for the second dataclass, like this:

from dataclasses import dataclass

@dataclass
class A:
    a: int = 1

@dataclass(init=False)
class B(A):
    b: int
    def __init__(self, *args, **kwargs): ...

There error disappeared.

I think the error comes from the process where dataclass tries to generate the default __init__() and checks the fields. But, if I understand correctly, this should not happen when the class already defines its own __init__().

Although I'm not sure whether the behavior should be defined as a bug, I do find the results inconsistant with the docs, which says

If the class already defines __init__(), this parameter (init) is ignored.

so, intuitively, adding init=False shouldn't have changed the result.

CPython versions tested on:

3.12, 3.13, 3.14

Operating systems tested on:

Windows

Linked PRs

Activity

  1. tonyyuyiding commented on Apr 23, 2026

    @tonyyuyiding
    Author

    btw I would be happy to try to fix it, if needed

  2. DavidCEllis commented on Apr 24, 2026

    @DavidCEllis
    Contributor

    Yes, this happens because _process_class only looks at the provided arguments and not the class attributes before adding the methods to the _FuncBuilder. The checks for methods that already exist are right at the end of the process.

    This is true for all of the methods and not just __init__, but _init_fn does some early checks before adding anything to the _FuncBuilder which is why you're seeing this bug. Ideally I think we'd want to keep all of this logic together so all of the checks are still happening in the same place and we're not checking everything twice.

  3. DavidCEllis commented on Apr 24, 2026

    @DavidCEllis
    Contributor

    Looking at this a bit more, while I think actually generating the __init__ function anyway (which is what currently also occurs), is probably unnecessary I'm not actually as certain about this check.

    Take your original example and then subclass B:

    from dataclasses import dataclass
    
    @dataclass
    class A:
        a: int = 1
    
    @dataclass
    class B(A):
        b: int
        def __init__(self, *args, **kwargs): ...
    
    @dataclass
    class C(B):
        pass

    This would be an error as __init__ isn't defined in C and so it would be generated, but the logical 'breakage' occurred in B. The current behaviour requires you to 'flag' this to some extent by setting init=False on B.

  4. added 2 commits that reference this issue on Jun 9, 2026
  5. added a commit that references this issue on Jul 12, 2026
  6. added
    pendingThe issue will be closed if no feedback is provided
    on Jul 13, 2026
  7. picnixz commented on Jul 13, 2026

    @picnixz
    Member

    The docs also say

    If any of the added methods already exist in the class, the behavior depends on the parameter, as documented below. The decorator returns the same class that it is called on; no new class is created.

    So, I also find them inconsistent there.

  8. picnixz commented on Jul 13, 2026

    @picnixz
    Member

    Has the behavior changed in a way that was not documented? I know that we refactored dataclasses to make them faster so maybe something changed without us noticing it.

  9. DavidCEllis commented on Jul 13, 2026

    @DavidCEllis
    Contributor

    The behaviour has been the same since dataclasses were added as far as I can tell, this fails on 3.8 with the same error. For comparison this also doesn't work with attrs even with init=False as attrs always does the check (I believe it also makes the function and puts it somewhere else if __init__ exists though).

    In my opinion the 'correct' solution from the user's side would be to mark the class or individual fields accurately with kw_only or to not use dataclasses where the class isn't really structured like a dataclass.

    From our side I think always doing the check would be an unnecessary breaking change, the question is do we match the documented behaviour or document the actual behaviour. If so we should do it consistently for all of the special methods1.

    The decorator returns the same class that it is called on; no new class is created.

    I'll note that this part has been wrong since the addition of slots=True which does create a new class and tries to pretend it's the old class.

    Footnotes

    1. and I'll have to update my lazy method fork before proceeding on that ↩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pendingThe issue will be closed if no feedback is providedstdlibStandard Library Python modules in the Lib/ directorytopic-dataclassestype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions