[Python-checkins] bpo-33796: Ignore ClassVar for dataclasses.replace(). (GH-7488)

Eric V. Smith webhook-mailer at python.org
Thu Jun 7 14:44:09 EDT 2018


https://github.com/python/cpython/commit/e7adf2ba41832404100313f9ac9d9f7fabedc1fd
commit: e7adf2ba41832404100313f9ac9d9f7fabedc1fd
branch: master
author: Eric V. Smith <ericvsmith at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2018-06-07T14:43:59-04:00
summary:

bpo-33796: Ignore ClassVar for dataclasses.replace(). (GH-7488)

files:
M Lib/dataclasses.py
M Lib/test/test_dataclasses.py

diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 2c5593bfc50d..96bf6e1df47a 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -416,7 +416,7 @@ def _field_init(f, frozen, globals, self_name):
     # Only test this now, so that we can create variables for the
     # default.  However, return None to signify that we're not going
     # to actually do the assignment statement for InitVars.
-    if f._field_type == _FIELD_INITVAR:
+    if f._field_type is _FIELD_INITVAR:
         return None
 
     # Now, actually generate the field assignment.
@@ -1160,6 +1160,10 @@ class C:
     # If a field is not in 'changes', read its value from the provided obj.
 
     for f in getattr(obj, _FIELDS).values():
+        # Only consider normal fields or InitVars.
+        if f._field_type is _FIELD_CLASSVAR:
+            continue
+
         if not f.init:
             # Error if this field is specified in changes.
             if f.name in changes:
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index 7c39b79142b2..929793119d72 100755
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -1712,91 +1712,6 @@ class Parent(Generic[T]):
         # Check MRO resolution.
         self.assertEqual(Child.__mro__, (Child, Parent, Generic, object))
 
-    def test_helper_replace(self):
-        @dataclass(frozen=True)
-        class C:
-            x: int
-            y: int
-
-        c = C(1, 2)
-        c1 = replace(c, x=3)
-        self.assertEqual(c1.x, 3)
-        self.assertEqual(c1.y, 2)
-
-    def test_helper_replace_frozen(self):
-        @dataclass(frozen=True)
-        class C:
-            x: int
-            y: int
-            z: int = field(init=False, default=10)
-            t: int = field(init=False, default=100)
-
-        c = C(1, 2)
-        c1 = replace(c, x=3)
-        self.assertEqual((c.x, c.y, c.z, c.t), (1, 2, 10, 100))
-        self.assertEqual((c1.x, c1.y, c1.z, c1.t), (3, 2, 10, 100))
-
-
-        with self.assertRaisesRegex(ValueError, 'init=False'):
-            replace(c, x=3, z=20, t=50)
-        with self.assertRaisesRegex(ValueError, 'init=False'):
-            replace(c, z=20)
-            replace(c, x=3, z=20, t=50)
-
-        # Make sure the result is still frozen.
-        with self.assertRaisesRegex(FrozenInstanceError, "cannot assign to field 'x'"):
-            c1.x = 3
-
-        # Make sure we can't replace an attribute that doesn't exist,
-        #  if we're also replacing one that does exist.  Test this
-        #  here, because setting attributes on frozen instances is
-        #  handled slightly differently from non-frozen ones.
-        with self.assertRaisesRegex(TypeError, r"__init__\(\) got an unexpected "
-                                             "keyword argument 'a'"):
-            c1 = replace(c, x=20, a=5)
-
-    def test_helper_replace_invalid_field_name(self):
-        @dataclass(frozen=True)
-        class C:
-            x: int
-            y: int
-
-        c = C(1, 2)
-        with self.assertRaisesRegex(TypeError, r"__init__\(\) got an unexpected "
-                                    "keyword argument 'z'"):
-            c1 = replace(c, z=3)
-
-    def test_helper_replace_invalid_object(self):
-        @dataclass(frozen=True)
-        class C:
-            x: int
-            y: int
-
-        with self.assertRaisesRegex(TypeError, 'dataclass instance'):
-            replace(C, x=3)
-
-        with self.assertRaisesRegex(TypeError, 'dataclass instance'):
-            replace(0, x=3)
-
-    def test_helper_replace_no_init(self):
-        @dataclass
-        class C:
-            x: int
-            y: int = field(init=False, default=10)
-
-        c = C(1)
-        c.y = 20
-
-        # Make sure y gets the default value.
-        c1 = replace(c, x=5)
-        self.assertEqual((c1.x, c1.y), (5, 10))
-
-        # Trying to replace y is an error.
-        with self.assertRaisesRegex(ValueError, 'init=False'):
-            replace(c, x=2, y=30)
-            with self.assertRaisesRegex(ValueError, 'init=False'):
-                replace(c, y=30)
-
     def test_dataclassses_pickleable(self):
         global P, Q, R
         @dataclass
@@ -3003,6 +2918,126 @@ def test_funny_class_names_names(self):
                 C = make_dataclass(classname, ['a', 'b'])
                 self.assertEqual(C.__name__, classname)
 
+class TestReplace(unittest.TestCase):
+    def test(self):
+        @dataclass(frozen=True)
+        class C:
+            x: int
+            y: int
+
+        c = C(1, 2)
+        c1 = replace(c, x=3)
+        self.assertEqual(c1.x, 3)
+        self.assertEqual(c1.y, 2)
+
+    def test_frozen(self):
+        @dataclass(frozen=True)
+        class C:
+            x: int
+            y: int
+            z: int = field(init=False, default=10)
+            t: int = field(init=False, default=100)
+
+        c = C(1, 2)
+        c1 = replace(c, x=3)
+        self.assertEqual((c.x, c.y, c.z, c.t), (1, 2, 10, 100))
+        self.assertEqual((c1.x, c1.y, c1.z, c1.t), (3, 2, 10, 100))
+
+
+        with self.assertRaisesRegex(ValueError, 'init=False'):
+            replace(c, x=3, z=20, t=50)
+        with self.assertRaisesRegex(ValueError, 'init=False'):
+            replace(c, z=20)
+            replace(c, x=3, z=20, t=50)
+
+        # Make sure the result is still frozen.
+        with self.assertRaisesRegex(FrozenInstanceError, "cannot assign to field 'x'"):
+            c1.x = 3
+
+        # Make sure we can't replace an attribute that doesn't exist,
+        #  if we're also replacing one that does exist.  Test this
+        #  here, because setting attributes on frozen instances is
+        #  handled slightly differently from non-frozen ones.
+        with self.assertRaisesRegex(TypeError, r"__init__\(\) got an unexpected "
+                                             "keyword argument 'a'"):
+            c1 = replace(c, x=20, a=5)
+
+    def test_invalid_field_name(self):
+        @dataclass(frozen=True)
+        class C:
+            x: int
+            y: int
+
+        c = C(1, 2)
+        with self.assertRaisesRegex(TypeError, r"__init__\(\) got an unexpected "
+                                    "keyword argument 'z'"):
+            c1 = replace(c, z=3)
+
+    def test_invalid_object(self):
+        @dataclass(frozen=True)
+        class C:
+            x: int
+            y: int
+
+        with self.assertRaisesRegex(TypeError, 'dataclass instance'):
+            replace(C, x=3)
+
+        with self.assertRaisesRegex(TypeError, 'dataclass instance'):
+            replace(0, x=3)
+
+    def test_no_init(self):
+        @dataclass
+        class C:
+            x: int
+            y: int = field(init=False, default=10)
+
+        c = C(1)
+        c.y = 20
+
+        # Make sure y gets the default value.
+        c1 = replace(c, x=5)
+        self.assertEqual((c1.x, c1.y), (5, 10))
+
+        # Trying to replace y is an error.
+        with self.assertRaisesRegex(ValueError, 'init=False'):
+            replace(c, x=2, y=30)
+
+        with self.assertRaisesRegex(ValueError, 'init=False'):
+            replace(c, y=30)
+
+    def test_classvar(self):
+        @dataclass
+        class C:
+            x: int
+            y: ClassVar[int] = 1000
+
+        c = C(1)
+        d = C(2)
+
+        self.assertIs(c.y, d.y)
+        self.assertEqual(c.y, 1000)
+
+        # Trying to replace y is an error: can't replace ClassVars.
+        with self.assertRaisesRegex(TypeError, r"__init__\(\) got an "
+                                    "unexpected keyword argument 'y'"):
+            replace(c, y=30)
+
+        replace(c, x=5)
+
+    ## def test_initvar(self):
+    ##     @dataclass
+    ##     class C:
+    ##         x: int
+    ##         y: InitVar[int]
+
+    ##     c = C(1, 10)
+    ##     d = C(2, 20)
+
+    ##     # In our case, replacing an InitVar is a no-op
+    ##     self.assertEqual(c, replace(c, y=5))
+
+    ##     replace(c, x=5)
+
 
 if __name__ == '__main__':
     unittest.main()



More information about the Python-checkins mailing list