From b4b243902d101415eef37a6c70880632c1022202 Mon Sep 17 00:00:00 2001 From: nahcmon Date: Tue, 9 Jun 2026 18:42:50 +0200 Subject: [PATCH 1/2] gh-148941: Skip generating __init__ when class already defines it (GH-148941) When @dataclass(init=True) is applied to a class that already defines __init__ in its own __dict__, the generated __init__ is always discarded by _set_new_attribute. However, _init_fn was still called unconditionally, and its field-ordering validation raised TypeError for inherited fields with defaults followed by fields without. The docs state "If the class already defines __init__(), this parameter is ignored." Align the implementation with that documented behaviour by checking '__init__' not in cls.__dict__ before calling _init_fn. --- Lib/dataclasses.py | 10 +++++----- Lib/test/test_dataclasses/__init__.py | 20 +++++++++++++++++++ ...-06-09-16-41-45.gh-issue-148941.aB3kLm.rst | 3 +++ 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-09-16-41-45.gh-issue-148941.aB3kLm.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 035678d902adaf9..0d40e4de85827e0 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -520,7 +520,7 @@ def add_fns_to_class(self, cls): if already_exists and (msg_extra := self.overwrite_errors.get(name)): error_msg = (f'Cannot overwrite attribute {fn.__name__} ' f'in class {cls.__name__}') - if not msg_extra is True: + if msg_extra is not True: error_msg = f'{error_msg} {msg_extra}' raise TypeError(error_msg) @@ -729,14 +729,14 @@ def _frozen_set_del_attr(cls, fields, func_builder): ('self', 'name', 'value'), (f' if {condition}:', ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', - f' super(__class__, self).__setattr__(name, value)'), + ' super(__class__, self).__setattr__(name, value)'), locals=locals, overwrite_error=True) func_builder.add_fn('__delattr__', ('self', 'name'), (f' if {condition}:', ' raise FrozenInstanceError(f"cannot delete field {name!r}")', - f' super(__class__, self).__delattr__(name)'), + ' super(__class__, self).__delattr__(name)'), locals=locals, overwrite_error=True) @@ -1099,7 +1099,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, # Do we have any Field members that don't also have annotations? for name, value in cls.__dict__.items(): - if isinstance(value, Field) and not name in cls_annotations: + if isinstance(value, Field) and name not in cls_annotations: raise TypeError(f'{name!r} is a field but has no type annotation') # Check rules that apply if we are derived from any dataclasses. @@ -1142,7 +1142,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, func_builder = _FuncBuilder(globals) - if init: + if init and '__init__' not in cls.__dict__: # Does this class have a post-init function? has_post_init = hasattr(cls, _POST_INIT_NAME) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 2468e3e64dd621c..c464b145adbaed8 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -2502,6 +2502,26 @@ def __init__(self, x): self.x = 2 * x self.assertEqual(C(5).x, 10) + def test_overwriting_init_with_field_ordering_conflict(self): + # gh-148941: When a class defines its own __init__, @dataclass must + # not raise TypeError due to field ordering issues in the generated + # __init__ signature — it would be discarded anyway. + @dataclass + class Base: + x: int = 0 # has a default + + # Without a user-defined __init__, this would raise TypeError + # ("non-default argument 'y' follows default argument 'x'"). + @dataclass + class Child(Base): + y: int # no default — would produce invalid signature + def __init__(self, y): + self.y = y + + self.assertEqual(Child(5).y, 5) + # Verify it's Child's own __init__, not a generated one. + self.assertNotIn('x', vars(Child(5))) + def test_inherit_from_protocol(self): # Dataclasses inheriting from protocol should preserve their own `__init__`. # See bpo-45081. diff --git a/Misc/NEWS.d/next/Library/2026-06-09-16-41-45.gh-issue-148941.aB3kLm.rst b/Misc/NEWS.d/next/Library/2026-06-09-16-41-45.gh-issue-148941.aB3kLm.rst new file mode 100644 index 000000000000000..37648b7ca979f78 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-09-16-41-45.gh-issue-148941.aB3kLm.rst @@ -0,0 +1,3 @@ +Fix :func:`dataclasses.dataclass` raising :exc:`TypeError` about field +ordering when a subclass defines its own :meth:`~object.__init__` and +inherits from a dataclass that has fields with default values. From 8429874e43fbeb97793c01cae934e7497a9c1dc2 Mon Sep 17 00:00:00 2001 From: nahcmon Date: Tue, 9 Jun 2026 20:08:51 +0200 Subject: [PATCH 2/2] Revert unrelated ruff style changes from previous commit --- Lib/dataclasses.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 0d40e4de85827e0..b45e09cbf1d22d5 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -520,7 +520,7 @@ def add_fns_to_class(self, cls): if already_exists and (msg_extra := self.overwrite_errors.get(name)): error_msg = (f'Cannot overwrite attribute {fn.__name__} ' f'in class {cls.__name__}') - if msg_extra is not True: + if not msg_extra is True: error_msg = f'{error_msg} {msg_extra}' raise TypeError(error_msg) @@ -729,14 +729,14 @@ def _frozen_set_del_attr(cls, fields, func_builder): ('self', 'name', 'value'), (f' if {condition}:', ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', - ' super(__class__, self).__setattr__(name, value)'), + f' super(__class__, self).__setattr__(name, value)'), locals=locals, overwrite_error=True) func_builder.add_fn('__delattr__', ('self', 'name'), (f' if {condition}:', ' raise FrozenInstanceError(f"cannot delete field {name!r}")', - ' super(__class__, self).__delattr__(name)'), + f' super(__class__, self).__delattr__(name)'), locals=locals, overwrite_error=True) @@ -1099,7 +1099,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, # Do we have any Field members that don't also have annotations? for name, value in cls.__dict__.items(): - if isinstance(value, Field) and name not in cls_annotations: + if isinstance(value, Field) and not name in cls_annotations: raise TypeError(f'{name!r} is a field but has no type annotation') # Check rules that apply if we are derived from any dataclasses.