10000 bpo-32896: Fix error when subclassing a dataclass with a field that u… · python/cpython@8f6eccd · GitHub
[go: up one dir, main page]

Skip to content

Commit 8f6eccd

Browse files
authored
bpo-32896: Fix error when subclassing a dataclass with a field that uses a default_factory (GH-6170)
Fix the way that new annotations in a class are detected.
1 parent 10b134a commit 8f6eccd

File tree

3 files changed

+63
-11
lines changed

3 files changed

+63
-11
lines changed

Lib/dataclasses.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -574,17 +574,18 @@ def _get_field(cls, a_name, a_type):
574574

575575
def _find_fields(cls):
576576
# Return a list of Field objects, in order, for this class (and no
577-
# base classes). Fields are found from __annotations__ (which is
578-
# guaranteed to be ordered). Default values are from class
579-
# attributes, if a field has a default. If the default value is
580-
# a Field(), then it contains additional info beyond (and
581-
# possibly including) the actual default value. Pseudo-fields
582-
# ClassVars and InitVars are included, despite the fact that
583-
# they're not real fields. That's dealt with later.
584-
585-
annotations = getattr(cls, '__annotations__', {})
586-
return [_get_field(cls, a_name, a_type)
587-
for a_name, a_type in annotations.items()]
577+
# base classes). Fields are found from the class dict's
578+
# __annotations__ (which is guaranteed to be ordered). Default
579+
# values are from class attributes, if a field has a default. If
580+
# the default value is a Field(), then it contains additional
581+
# info beyond (and possibly including) the actual default value.
582+
# Pseudo-fields ClassVars and InitVars are included, despite the
583+
# fact that they're not real fields. That's dealt with later.
584+
585+
# If __annotations__ isn't present, then this class adds no new
586+
# annotations.
587+
annotations = cls.__dict__.get('__annotations__', {})
588+
return [_get_field(cls, name, type) for name, type in annotations.items()]
588589

589590

590591
def _set_new_attribute(cls, name, value):

Lib/test/test_dataclasses.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,55 @@ class C:
11471147
C().x
11481148
self.assertEqual(factory.call_count, 2)
11491149

1150+
def test_default_factory_derived(self):
1151+
# See bpo-32896.
1152+
@dataclass
1153+
class Foo:
1154+
x: dict = field(default_factory=dict)
1155+
1156+
@dataclass
1157+
class Bar(Foo):
1158+
y: int = 1
1159+
1160+
self.assertEqual(Foo().x, {})
1161+
self.assertEqual(Bar().x, {})
1162+
self.assertEqual(Bar().y, 1)
1163+
1164+
@dataclass
1165+
class Baz(Foo):
1166+
pass
1167+
self.assertEqual(Baz().x, {})
1168+
1169+
def test_intermediate_non_dataclass(self):
1170+
# Test that an intermediate class that defines
1171+
# annotations does not define fields.
1172+
1173+
@dataclass
1174+
class A:
1175+
x: int
1176+
1177+
class B(A):
1178+
y: int
1179+
1180+
@dataclass
1181+
class C(B):
1182+
z: int
1183+
1184+
c = C(1, 3)
1185+
self.assertEqual((c.x, c.z), (1, 3))
1186+
1187+
# .y was not initialized.
1188+
with self.assertRaisesRegex(AttributeError,
1189+
'object has no attribute'):
1190+
c.y
1191+
1192+
# And if we again derive a non-dataclass, no fields are added.
1193+
class D(C):
1194+
t: int
1195+
d = D(4, 5)
1196+
self.assertEqual((d.x, d.z), (4, 5))
1197+
1198+
11501199
def x_test_classvar_default_factory(self):
11511200
# XXX: it's an error for a ClassVar to have a factory function
11521201
@dataclass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an error where subclassing a dataclass with a field that uses a
2+
default_factory would generate an incorrect class.

0 commit comments

Comments
 (0)
0