Skip to content

Commit 280f94b

Browse files
committed
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.
1 parent 0fa06f4 commit 280f94b

3 files changed

Lines changed: 28 additions & 5 deletions

File tree

Lib/dataclasses.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ def add_fns_to_class(self, cls):
520520
if already_exists and (msg_extra := self.overwrite_errors.get(name)):
521521
error_msg = (f'Cannot overwrite attribute {fn.__name__} '
522522
f'in class {cls.__name__}')
523-
if not msg_extra is True:
523+
if msg_extra is not True:
524524
error_msg = f'{error_msg} {msg_extra}'
525525

526526
raise TypeError(error_msg)
@@ -729,14 +729,14 @@ def _frozen_set_del_attr(cls, fields, func_builder):
729729
('self', 'name', 'value'),
730730
(f' if {condition}:',
731731
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
732-
f' super(__class__, self).__setattr__(name, value)'),
732+
' super(__class__, self).__setattr__(name, value)'),
733733
locals=locals,
734734
overwrite_error=True)
735735
func_builder.add_fn('__delattr__',
736736
('self', 'name'),
737737
(f' if {condition}:',
738738
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
739-
f' super(__class__, self).__delattr__(name)'),
739+
' super(__class__, self).__delattr__(name)'),
740740
locals=locals,
741741
overwrite_error=True)
742742

@@ -1099,7 +1099,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
10991099

11001100
# Do we have any Field members that don't also have annotations?
11011101
for name, value in cls.__dict__.items():
1102-
if isinstance(value, Field) and not name in cls_annotations:
1102+
if isinstance(value, Field) and name not in cls_annotations:
11031103
raise TypeError(f'{name!r} is a field but has no type annotation')
11041104

11051105
# 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,
11421142

11431143
func_builder = _FuncBuilder(globals)
11441144

1145-
if init:
1145+
if init and '__init__' not in cls.__dict__:
11461146
# Does this class have a post-init function?
11471147
has_post_init = hasattr(cls, _POST_INIT_NAME)
11481148

Lib/test/test_dataclasses/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2502,6 +2502,26 @@ def __init__(self, x):
25022502
self.x = 2 * x
25032503
self.assertEqual(C(5).x, 10)
25042504

2505+
def test_overwriting_init_with_field_ordering_conflict(self):
2506+
# gh-148941: When a class defines its own __init__, @dataclass must
2507+
# not raise TypeError due to field ordering issues in the generated
2508+
# __init__ signature — it would be discarded anyway.
2509+
@dataclass
2510+
class Base:
2511+
x: int = 0 # has a default
2512+
2513+
# Without a user-defined __init__, this would raise TypeError
2514+
# ("non-default argument 'y' follows default argument 'x'").
2515+
@dataclass
2516+
class Child(Base):
2517+
y: int # no default — would produce invalid signature
2518+
def __init__(self, y):
2519+
self.y = y
2520+
2521+
self.assertEqual(Child(5).y, 5)
2522+
# Verify it's Child's own __init__, not a generated one.
2523+
self.assertNotIn('x', vars(Child(5)))
2524+
25052525
def test_inherit_from_protocol(self):
25062526
# Dataclasses inheriting from protocol should preserve their own `__init__`.
25072527
# See bpo-45081.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`dataclasses.dataclass` raising :exc:`TypeError` about field
2+
ordering when a subclass defines its own :meth:`~object.__init__` and
3+
inherits from a dataclass that has fields with default values.

0 commit comments

Comments
 (0)