diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 7a725dfb5208bbc..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', @@ -142,7 +141,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 +285,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} @@ -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 @@ -571,6 +569,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 @@ -697,14 +697,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_dataclass('C', [('a', int), ('b', int, Field(init=False))], bases=Base) is equivalent to: @@ -722,14 +722,14 @@ 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: 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.