]> granicus.if.org Git - python/commitdiff
bpo-33100: Dataclasses now handles __slots__ and default values correctly. (GH-6152)
authorEric V. Smith <ericvsmith@users.noreply.github.com>
Tue, 20 Mar 2018 01:07:51 +0000 (21:07 -0400)
committerGitHub <noreply@github.com>
Tue, 20 Mar 2018 01:07:51 +0000 (21:07 -0400)
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
Lib/test/test_dataclasses.py
Misc/NEWS.d/next/Library/2018-03-19-20-47-00.bpo-33100.chyIO4.rst [new file with mode: 0644]

index 8ab04dd5b975e9996e2737cdfe6cebda5736435f..a4afd50376bdc6fa861bbfafb0276ac0e4a8c0ac 100644 (file)
@@ -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.
index 3e6726360940675def27fee4f1e5b47bf392ff7a..db03ec1925f6742a3e28dfab601b61dc31a0080d 100755 (executable)
@@ -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 (file)
index 0000000..080a55c
--- /dev/null
@@ -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.