Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,16 +828,11 @@ def _get_field(cls, a_name, a_type, default_kw_only):
# default_kw_only is the value of kw_only to use if there isn't a field()
# that defines it.

# If the default value isn't derived from Field, then it's only a
# normal default value. Convert it to a Field().
default = getattr(cls, a_name, MISSING)
if isinstance(default, Field):
f = default
member = vars(cls).get(a_name, MISSING)
if isinstance(member, Field):
f = member
else:
if isinstance(default, types.MemberDescriptorType):
# This is a field in __slots__, so it has no default value.
default = MISSING
f = field(default=default)
f = field()

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

# kw_only validation and assignment.
if f._field_type in (_FIELD, _FIELD_INITVAR):
# If the default value isn't derived from Field, then it's only a
# normal default value.
default = getattr(cls, a_name, MISSING)

# This is a field in __slots__, so it has no default value.
if isinstance(default, types.MemberDescriptorType):
default = MISSING

if not isinstance(default, Field):
f.default = default

# For real and InitVar fields, if kw_only wasn't specified use the
# default value.
if f.kw_only is MISSING:
Expand Down Expand Up @@ -1072,6 +1078,14 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
# field) exists and is of type 'Field', replace it with the
# real default. This is so that normal class introspection
# sees a real default value, not a Field.

# Class variables cannot be removed from the class.
if f._field_type is _FIELD_CLASSVAR:
if f.default is not MISSING:
setattr(cls, f.name, f.default)
continue

# Other fields can be set or removed as necessary.
if isinstance(getattr(cls, f.name, None), Field):
if f.default is MISSING:
# If there's no default, delete the class attribute.
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,27 @@ class D(C):
d = D(4, 5)
self.assertEqual((d.x, d.z), (4, 5))

def test_classvar_default_value_failing_descriptor(self):
"""Regression test for GH-144618."""
class Kaboom:
def __get__(self, inst, owner):
raise RuntimeError("kaboom!")

@dataclass
class C:
kaboom: ClassVar[Kaboom] = Kaboom()

self.assertIsInstance(C.__dict__["kaboom"], Kaboom)

def test_classvar_member_isnt_tracked_or_removed(self):
"""Regression test for GH-144618."""
@dataclass
class C:
x: ClassVar[int] = 1000

self.assertIs(C.__dataclass_fields__['x'].default, MISSING)
self.assertEqual(C.x, 1000)

def test_classvar_default_factory(self):
# It's an error for a ClassVar to have a factory function.
with self.assertRaisesRegex(TypeError,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:deco:`dataclasses.dataclass` no longer triggers ``__get__`` of
:data:`~typing.ClassVar` members nor tracks them as field defaults. Patch by
Bartosz Sławecki.
Loading