From: Eric V. Smith Date: Mon, 3 May 2021 07:24:53 +0000 (-0400) Subject: bpo-44015: dataclasses should allow KW_ONLY to be specified only once per class ... X-Git-Tag: v3.10.0b1~11 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=99ad742ea913e421d012c1a623029eac31bdfe85;p=thirdparty%2FPython%2Fcpython.git bpo-44015: dataclasses should allow KW_ONLY to be specified only once per class (GH-25841) bpo-44015: Raise a TypeError if KW_ONLY is specified more than once. --- diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 363d0b66d208..cbba320e01a5 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -930,6 +930,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, # we can. cls_fields = [] # Get a reference to this module for the _is_kw_only() test. + KW_ONLY_seen = False dataclasses = sys.modules[__name__] for name, type in cls_annotations.items(): # See if this is a marker to change the value of kw_only. @@ -939,6 +940,10 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, _is_kw_only))): # Switch the default to kw_only=True, and ignore this # annotation: it's not a real field. + if KW_ONLY_seen: + raise TypeError(f'{name!r} is KW_ONLY, but KW_ONLY ' + 'has already been specified') + KW_ONLY_seen = True kw_only = True else: # Otherwise it's a field of some type. diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 16ee4c7705d8..8e645aeb4a75 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -3699,6 +3699,83 @@ class TestKeywordArgs(unittest.TestCase): self.assertEqual(c.b, 3) self.assertEqual(c.c, 2) + def test_KW_ONLY_as_string(self): + @dataclass + class A: + a: int + _: 'dataclasses.KW_ONLY' + b: int + c: int + A(3, c=5, b=4) + msg = "takes 2 positional arguments but 4 were given" + with self.assertRaisesRegex(TypeError, msg): + A(3, 4, 5) + + def test_KW_ONLY_twice(self): + msg = "'Y' is KW_ONLY, but KW_ONLY has already been specified" + + with self.assertRaisesRegex(TypeError, msg): + @dataclass + class A: + a: int + X: KW_ONLY + Y: KW_ONLY + b: int + c: int + + with self.assertRaisesRegex(TypeError, msg): + @dataclass + class A: + a: int + X: KW_ONLY + b: int + Y: KW_ONLY + c: int + + with self.assertRaisesRegex(TypeError, msg): + @dataclass + class A: + a: int + X: KW_ONLY + b: int + c: int + Y: KW_ONLY + + # But this usage is okay, since it's not using KW_ONLY. + @dataclass + class A: + a: int + _: KW_ONLY + b: int + c: int = field(kw_only=True) + + # And if inheriting, it's okay. + @dataclass + class A: + a: int + _: KW_ONLY + b: int + c: int + @dataclass + class B(A): + _: KW_ONLY + d: int + + # Make sure the error is raised in a derived class. + with self.assertRaisesRegex(TypeError, msg): + @dataclass + class A: + a: int + _: KW_ONLY + b: int + c: int + @dataclass + class B(A): + X: KW_ONLY + d: int + Y: KW_ONLY + + def test_post_init(self): @dataclass class A: diff --git a/Misc/NEWS.d/next/Library/2021-05-03-03-03-49.bpo-44015.V5936k.rst b/Misc/NEWS.d/next/Library/2021-05-03-03-03-49.bpo-44015.V5936k.rst new file mode 100644 index 000000000000..4c4f543fa0c2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-03-03-03-49.bpo-44015.V5936k.rst @@ -0,0 +1 @@ +In @dataclass(), raise a TypeError if KW_ONLY is specified more than once.