From ed7d429ebb591f65cef558760fb4ebdc4fc8f8b0 Mon Sep 17 00:00:00 2001 From: "Eric V. Smith" Date: Sat, 6 Jan 2018 16:14:03 -0500 Subject: [PATCH] bpo-32278: Allow dataclasses.make_dataclass() to omit type information. (gh-5115) --- Lib/dataclasses.py | 20 +++++++++++++------ Lib/test/test_dataclasses.py | 14 +++++++++++++ .../2018-01-06-15-15-34.bpo-32278.bGnGc0.rst | 2 ++ 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-01-06-15-15-34.bpo-32278.bGnGc0.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index b4786bf502e..c4e94b8f667 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -708,9 +708,10 @@ def _astuple_inner(obj, tuple_factory): def make_dataclass(cls_name, fields, *, bases=(), namespace=None): """Return a new dynamically created dataclass. - The dataclass name will be 'cls_name'. 'fields' is an interable - of either (name, type) or (name, type, Field) objects. Field - objects are created by calling 'field(name, type [, Field])'. + The dataclass name will be 'cls_name'. 'fields' is an iterable + of either (name), (name, type) or (name, type, Field) objects. If type is + omitted, use the string 'typing.Any'. Field objects are created by + calling 'field(name, type [, Field])'. C = make_class('C', [('a', int', ('b', int, Field(init=False))], bases=Base) @@ -730,12 +731,19 @@ def make_dataclass(cls_name, fields, *, bases=(), namespace=None): # Copy namespace since we're going to mutate it. namespace = namespace.copy() - anns = collections.OrderedDict((name, tp) for name, tp, *_ in fields) - namespace['__annotations__'] = anns + anns = collections.OrderedDict() for item in fields: - if len(item) == 3: + if isinstance(item, str): + name = item + tp = 'typing.Any' + elif len(item) == 2: + name, tp, = item + elif len(item) == 3: name, tp, spec = item namespace[name] = spec + anns[name] = tp + + namespace['__annotations__'] = anns cls = type(cls_name, bases, namespace) return dataclass(cls) diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index fca384d8c3c..c44c53d039d 100755 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -2033,6 +2033,20 @@ class TestCase(unittest.TestCase): self.assertEqual(C.y, 10) self.assertEqual(C.z, 20) + def test_helper_make_dataclass_no_types(self): + C = make_dataclass('Point', ['x', 'y', 'z']) + c = C(1, 2, 3) + self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3}) + self.assertEqual(C.__annotations__, {'x': 'typing.Any', + 'y': 'typing.Any', + 'z': 'typing.Any'}) + + C = make_dataclass('Point', ['x', ('y', int), 'z']) + c = C(1, 2, 3) + self.assertEqual(vars(c), {'x': 1, 'y': 2, 'z': 3}) + self.assertEqual(C.__annotations__, {'x': 'typing.Any', + 'y': int, + 'z': 'typing.Any'}) class TestDocString(unittest.TestCase): def assertDocStrEqual(self, a, b): diff --git a/Misc/NEWS.d/next/Library/2018-01-06-15-15-34.bpo-32278.bGnGc0.rst b/Misc/NEWS.d/next/Library/2018-01-06-15-15-34.bpo-32278.bGnGc0.rst new file mode 100644 index 00000000000..c627468072e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-01-06-15-15-34.bpo-32278.bGnGc0.rst @@ -0,0 +1,2 @@ +Make type information optional on dataclasses.make_dataclass(). If omitted, +the string 'typing.Any' is used.