summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric V. Smith <ericvsmith@users.noreply.github.com>2021-05-03 07:24:53 (GMT)
committerGitHub <noreply@github.com>2021-05-03 07:24:53 (GMT)
commit99ad742ea913e421d012c1a623029eac31bdfe85 (patch)
treec4645c0b2590b0c8c082986e6fd390ab7304e80c
parent72720a2639368436da0981983549d000170623cc (diff)
downloadcpython-99ad742ea913e421d012c1a623029eac31bdfe85.zip
cpython-99ad742ea913e421d012c1a623029eac31bdfe85.tar.gz
cpython-99ad742ea913e421d012c1a623029eac31bdfe85.tar.bz2
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.
-rw-r--r--Lib/dataclasses.py5
-rw-r--r--Lib/test/test_dataclasses.py77
-rw-r--r--Misc/NEWS.d/next/Library/2021-05-03-03-03-49.bpo-44015.V5936k.rst1
3 files changed, 83 insertions, 0 deletions
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 363d0b6..cbba320 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 16ee4c7..8e645ae 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 0000000..4c4f543
--- /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.