Skip to content

bpo-44649: Fix dataclasses(slots=True) with a field with a default, but init=False #29692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 22, 2021
Merged
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
19 changes: 13 additions & 6 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ def _field_assign(frozen, name, value, self_name):
return f'{self_name}.{name}={value}'


def _field_init(f, frozen, globals, self_name):
def _field_init(f, frozen, globals, self_name, slots):
# Return the text of the line in the body of __init__ that will
# initialize this field.

Expand Down Expand Up @@ -487,9 +487,15 @@ def _field_init(f, frozen, globals, self_name):
globals[default_name] = f.default
value = f.name
else:
# This field does not need initialization. Signify that
# to the caller by returning None.
return None
# If the class has slots, then initialize this field.
if slots and f.default is not MISSING:
globals[default_name] = f.default
value = default_name
else:
# This field does not need initialization: reading from it will
# just use the class attribute that contains the default.
# Signify that to the caller by returning None.
return None

# Only test this now, so that we can create variables for the
# default. However, return None to signify that we're not going
Expand Down Expand Up @@ -521,7 +527,7 @@ def _init_param(f):


def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,
self_name, globals):
self_name, globals, slots):
# fields contains both real fields and InitVar pseudo-fields.

# Make sure we don't have fields without defaults following fields
Expand All @@ -548,7 +554,7 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,

body_lines = []
for f in fields:
line = _field_init(f, frozen, locals, self_name)
line = _field_init(f, frozen, locals, self_name, slots)
# line is None means that this field doesn't require
# initialization (it's a pseudo-field). Just skip it.
if line:
Expand Down Expand Up @@ -1027,6 +1033,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
'__dataclass_self__' if 'self' in fields
else 'self',
globals,
slots,
))

# Get the fields as a list, and include only real fields. This is
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2880,6 +2880,28 @@ def test_frozen_pickle(self):
self.assertIsNot(obj, p)
self.assertEqual(obj, p)

def test_slots_with_default_no_init(self):
# Originally reported in bpo-44649.
@dataclass(slots=True)
class A:
a: str
b: str = field(default='b', init=False)

obj = A("a")
self.assertEqual(obj.a, 'a')
self.assertEqual(obj.b, 'b')

def test_slots_with_default_factory_no_init(self):
# Originally reported in bpo-44649.
@dataclass(slots=True)
class A:
a: str
b: str = field(default_factory=lambda:'b', init=False)

obj = A("a")
self.assertEqual(obj.a, 'a')
self.assertEqual(obj.b, 'b')

class TestDescriptors(unittest.TestCase):
def test_set_name(self):
# See bpo-33141.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Handle dataclass(slots=True) with a field that has default a default value,
but for which init=False.