[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