From 7389fd935c95b4b6f094312294e703ee0de18719 Mon Sep 17 00:00:00 2001 From: "Eric V. Smith" Date: Mon, 19 Mar 2018 21:07:51 -0400 Subject: bpo-33100: Dataclasses now handles __slots__ and default values correctly. (GH-6152) If the class has a member that's a MemberDescriptorType, it's not a default value, it's from that member being in __slots__. --- Lib/dataclasses.py | 3 ++ Lib/test/test_dataclasses.py | 42 ++++++++++++++++++++++ .../2018-03-19-20-47-00.bpo-33100.chyIO4.rst | 2 ++ 3 files changed, 47 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2018-03-19-20-47-00.bpo-33100.chyIO4.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 8ab04dd..a4afd50 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -519,6 +519,9 @@ def _get_field(cls, a_name, a_type): if isinstance(default, Field): f = default else: + if isinstance(default, types.MemberDescriptorType): + # This is a field in __slots__, so it has no default value. + default = MISSING f = field(default=default) # Assume it's a normal field until proven otherwise. diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 3e67263..db03ec1 100755 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -2564,5 +2564,47 @@ class TestFrozen(unittest.TestCase): self.assertEqual(s.cached, True) +class TestSlots(unittest.TestCase): + def test_simple(self): + @dataclass + class C: + __slots__ = ('x',) + x: Any + + # There was a bug where a variable in a slot was assumed + # to also have a default value (of type types.MemberDescriptorType). + with self.assertRaisesRegex(TypeError, + "__init__\(\) missing 1 required positional argument: 'x'"): + C() + + # We can create an instance, and assign to x. + c = C(10) + self.assertEqual(c.x, 10) + c.x = 5 + self.assertEqual(c.x, 5) + + # We can't assign to anything else. + with self.assertRaisesRegex(AttributeError, "'C' object has no attribute 'y'"): + c.y = 5 + + def test_derived_added_field(self): + # See bpo-33100. + @dataclass + class Base: + __slots__ = ('x',) + x: Any + + @dataclass + class Derived(Base): + x: int + y: int + + d = Derived(1, 2) + self.assertEqual((d.x, d.y), (1, 2)) + + # We can add a new field to the derived instance. + d.z = 10 + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2018-03-19-20-47-00.bpo-33100.chyIO4.rst b/Misc/NEWS.d/next/Library/2018-03-19-20-47-00.bpo-33100.chyIO4.rst new file mode 100644 index 0000000..080a55c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-03-19-20-47-00.bpo-33100.chyIO4.rst @@ -0,0 +1,2 @@ +Dataclasses: If a field has a default value that's a MemberDescriptorType, +then it's from that field being in __slots__, not an actual default value. -- cgit v0.12