From 3c25f776efb2a1cf1dd2b8e62a0690a1890aedd8 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 4 Dec 2017 22:05:53 -0800 Subject: [PATCH 1/3] Misc questions, suggestions, and error corrections --- Lib/dataclasses.py | 15 ++++++++++----- Lib/test/test_dataclasses.py | 4 ++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 7a725dfb5208bbc..04754ef4c518f40 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -142,7 +142,7 @@ def _tuple_str(obj_name, fields): # return "(self.x,self.y)". # Special case for the 0-tuple. - if len(fields) == 0: + if not fields: return '()' # Note the trailing comma, needed if this turns out to be a 1-tuple. return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)' @@ -286,7 +286,7 @@ def _init_fn(fields, frozen, has_post_init, self_name): body_lines += [f'{self_name}.{_POST_INIT_NAME}({params_str})'] # If no body lines, use 'pass'. - if len(body_lines) == 0: + if not body_lines: body_lines = ['pass'] locals = {f'_type_{f.name}': f.type for f in fields} @@ -571,6 +571,8 @@ def _process_class(cls, repr, eq, order, hash, init, frozen): # _cls should never be specified by keyword, so start it with an # underscore. The presense of _cls is used to detect if this # decorator is being called with parameters or not. + +# Why hash=None instead of hash=False? def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False, hash=None, frozen=False): """Returns the same class as was passed in, with dunder methods @@ -619,6 +621,9 @@ def _isdataclass(obj): return not isinstance(obj, type) and hasattr(obj, _MARKER) +# Shouldn't the default factor be collections.OrderedDict()? +# The class itself is ordered and instances may or may not be +# intrinsically ordered depending on the "ordered" keyword. def asdict(obj, *, dict_factory=dict): """Return the fields of a dataclass instance as a new dictionary mapping field names to field values. @@ -697,14 +702,14 @@ def _astuple_inner(obj, tuple_factory): return deepcopy(obj) -def make_dataclass(cls_name, fields, *, bases=(), namespace=None): +def make_dataclass(cls_name, fields, *, bases=(), namespace=None, **kwargs): """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])'. - C = make_class('C', [('a', int', ('b', int, Field(init=False))], bases=Base) + C = make_class('C', [('a', int), ('b', int, Field(init=False))], bases=Base) is equivalent to: @@ -729,7 +734,7 @@ class C(Base): name, tp, spec = item namespace[name] = spec cls = type(cls_name, bases, namespace) - return dataclass(cls) + return dataclass(cls, **kwargs) def replace(obj, **changes): diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index caea98a13b06b7e..c33dad886792d52 100755 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -1915,6 +1915,10 @@ def test_helper_make_dataclass(self): self.assertEqual((c.x, c.y), (10, 5)) self.assertEqual(c.add_one(), 11) + def test_helper_make_dataclass_keywords(self): + C = make_dataclass('C', [('x', int), ('y', int)], order=True, hash=True) + self.assertLess(C(10, 20), C(10, 25)) + self.assertEqual(hash(C(10, 20)), hash(C(10, 20))) def test_helper_make_dataclass_no_mutate_namespace(self): # Make sure a provided namespace isn't mutated. From 894560e1a7c9bde9b80adb3fc7e5b9f28eb26967 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 4 Dec 2017 22:19:06 -0800 Subject: [PATCH 2/3] Fix additional typo: make_class -> make_dataclass --- Lib/dataclasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 04754ef4c518f40..e27ef7c6fdf8056 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -709,7 +709,7 @@ def make_dataclass(cls_name, fields, *, bases=(), namespace=None, **kwargs): of either (name, type) or (name, type, Field) objects. Field objects are created by calling 'field(name, type [, Field])'. - C = make_class('C', [('a', int), ('b', int, Field(init=False))], bases=Base) + C = make_dataclass('C', [('a', int), ('b', int, Field(init=False))], bases=Base) is equivalent to: From 7fd6630d731c0e8605fe667eccf800bfd7ad7422 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 15 Dec 2017 10:19:52 -0800 Subject: [PATCH 3/3] OrderedDict is no longer needed since regular dicts guarantee order --- Lib/dataclasses.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index e27ef7c6fdf8056..45bb9b6a386bb5a 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1,7 +1,6 @@ import sys import types from copy import deepcopy -import collections import inspect __all__ = ['dataclass', @@ -446,11 +445,10 @@ def _set_attribute(cls, name, value): def _process_class(cls, repr, eq, order, hash, init, frozen): - # Use an OrderedDict because: - # - Order matters! + # Note that order matters here. # - Derived class fields overwrite base class fields, but the # order is defined by the base class, which is found first. - fields = collections.OrderedDict() + fields = {} # Find our base classes in reverse MRO order, and exclude # ourselves. In reversed order so that more derived classes @@ -621,9 +619,6 @@ def _isdataclass(obj): return not isinstance(obj, type) and hasattr(obj, _MARKER) -# Shouldn't the default factor be collections.OrderedDict()? -# The class itself is ordered and instances may or may not be -# intrinsically ordered depending on the "ordered" keyword. def asdict(obj, *, dict_factory=dict): """Return the fields of a dataclass instance as a new dictionary mapping field names to field values. @@ -727,7 +722,7 @@ class C(Base): # Copy namespace since we're going to mutate it. namespace = namespace.copy() - anns = collections.OrderedDict((name, tp) for name, tp, *_ in fields) + anns = {name : tp for name, tp, *_ in fields} namespace['__annotations__'] = anns for item in fields: if len(item) == 3: