Skip to content

Commit 9d89c56

Browse files
committed
Don't track ClassVar dataclass members as defaults
1 parent d736349 commit 9d89c56

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

Lib/dataclasses.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -828,16 +828,11 @@ def _get_field(cls, a_name, a_type, default_kw_only):
828828
# default_kw_only is the value of kw_only to use if there isn't a field()
829829
# that defines it.
830830

831-
# If the default value isn't derived from Field, then it's only a
832-
# normal default value. Convert it to a Field().
833-
default = getattr(cls, a_name, MISSING)
834-
if isinstance(default, Field):
835-
f = default
831+
member = vars(cls).get(a_name, MISSING)
832+
if isinstance(member, Field):
833+
f = member
836834
else:
837-
if isinstance(default, types.MemberDescriptorType):
838-
# This is a field in __slots__, so it has no default value.
839-
default = MISSING
840-
f = field(default=default)
835+
f = field()
841836

842837
# Only at this point do we know the name and the type. Set them.
843838
f.name = a_name
@@ -899,6 +894,17 @@ def _get_field(cls, a_name, a_type, default_kw_only):
899894

900895
# kw_only validation and assignment.
901896
if f._field_type in (_FIELD, _FIELD_INITVAR):
897+
# If the default value isn't derived from Field, then it's only a
898+
# normal default value.
899+
default = getattr(cls, a_name, MISSING)
900+
901+
# This is a field in __slots__, so it has no default value.
902+
if isinstance(default, types.MemberDescriptorType):
903+
default = MISSING
904+
905+
if not isinstance(default, Field):
906+
f.default = default
907+
902908
# For real and InitVar fields, if kw_only wasn't specified use the
903909
# default value.
904910
if f.kw_only is MISSING:
@@ -1072,6 +1078,14 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
10721078
# field) exists and is of type 'Field', replace it with the
10731079
# real default. This is so that normal class introspection
10741080
# sees a real default value, not a Field.
1081+
1082+
# Class variables cannot be removed from the class.
1083+
if f._field_type is _FIELD_CLASSVAR:
1084+
if f.default is not MISSING:
1085+
setattr(cls, f.name, f.default)
1086+
continue
1087+
1088+
# Regular fields can be set or removed as necessary.
10751089
if isinstance(getattr(cls, f.name, None), Field):
10761090
if f.default is MISSING:
10771091
# If there's no default, delete the class attribute.

Lib/test/test_dataclasses/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,6 +1496,25 @@ class D(C):
14961496
d = D(4, 5)
14971497
self.assertEqual((d.x, d.z), (4, 5))
14981498

1499+
def test_classvar_default_value_failing_descriptor(self):
1500+
class Kaboom:
1501+
def __get__(self, inst, owner):
1502+
raise RuntimeError("kaboom!")
1503+
1504+
@dataclass
1505+
class C:
1506+
kaboom: ClassVar[None] = Kaboom()
1507+
1508+
self.assertIsInstance(C.__dict__["kaboom"], Kaboom)
1509+
1510+
def test_classvar_member_isnt_tracked_or_removed(self):
1511+
@dataclass
1512+
class C:
1513+
x: ClassVar[int] = 1000
1514+
1515+
self.assertEqual(C.__dataclass_fields__['x'].default, MISSING)
1516+
self.assertEqual(C.x, 1000)
1517+
14991518
def test_classvar_default_factory(self):
15001519
# It's an error for a ClassVar to have a factory function.
15011520
with self.assertRaisesRegex(TypeError,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:deco:`dataclasses.dataclass` no longer triggers ``__get__`` of
2+
:data:`~typing.ClassVar` members nor tracks them as field defaults. Patch by
3+
Bartosz Sławecki.

0 commit comments

Comments
 (0)