Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import sys
import types
from copy import deepcopy
import collections
import inspect

__all__ = ['dataclass',
Expand Down Expand Up @@ -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])},)'
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand All @@ -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):
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down