Skip to content
Merged
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
19 changes: 13 additions & 6 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
'astuple',
'make_dataclass',
'replace',
'is_dataclass',
]

# Raised when an attempt is made to modify a frozen class.
Expand Down Expand Up @@ -615,11 +616,17 @@ def fields(class_or_instance):
return tuple(f for f in fields.values() if f._field_type is _FIELD)


def _isdataclass(obj):
def _is_dataclass_instance(obj):
"""Returns True if obj is an instance of a dataclass."""
return not isinstance(obj, type) and hasattr(obj, _MARKER)


def is_dataclass(obj):
"""Returns True if obj is a dataclass or an instance of a
dataclass."""
return hasattr(obj, _MARKER)


def asdict(obj, *, dict_factory=dict):
"""Return the fields of a dataclass instance as a new dictionary mapping
field names to field values.
Expand All @@ -639,12 +646,12 @@ class C:
dataclass instances. This will also look into built-in containers:
tuples, lists, and dicts.
"""
if not _isdataclass(obj):
if not _is_dataclass_instance(obj):
raise TypeError("asdict() should be called on dataclass instances")
return _asdict_inner(obj, dict_factory)

def _asdict_inner(obj, dict_factory):
if _isdataclass(obj):
if _is_dataclass_instance(obj):
result = []
for f in fields(obj):
value = _asdict_inner(getattr(obj, f.name), dict_factory)
Expand Down Expand Up @@ -678,12 +685,12 @@ class C:
tuples, lists, and dicts.
"""

if not _isdataclass(obj):
if not _is_dataclass_instance(obj):
raise TypeError("astuple() should be called on dataclass instances")
return _astuple_inner(obj, tuple_factory)

def _astuple_inner(obj, tuple_factory):
if _isdataclass(obj):
if _is_dataclass_instance(obj):
result = []
for f in fields(obj):
value = _astuple_inner(getattr(obj, f.name), tuple_factory)
Expand Down Expand Up @@ -751,7 +758,7 @@ class C:
# We're going to mutate 'changes', but that's okay because it's a new
# dict, even if called with 'replace(obj, **my_changes)'.

if not _isdataclass(obj):
if not _is_dataclass_instance(obj):
raise TypeError("replace() should be called on dataclass instances")

# It's an error to have init=False fields in 'changes'.
Expand Down
37 changes: 21 additions & 16 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import (
dataclass, field, FrozenInstanceError, fields, asdict, astuple,
make_dataclass, replace, InitVar, Field, MISSING
make_dataclass, replace, InitVar, Field, MISSING, is_dataclass,
)

import pickle
Expand Down Expand Up @@ -1365,27 +1365,32 @@ class C:

self.assertIs(C().x, int)

def test_isdataclass(self):
# There is no isdataclass() helper any more, but the PEP
# describes how to write it, so make sure that works. Note
# that this version returns True for both classes and
# instances.
def isdataclass(obj):
try:
fields(obj)
return True
except TypeError:
return False
def test_is_dataclass(self):
class NotDataClass:
pass

self.assertFalse(isdataclass(0))
self.assertFalse(isdataclass(int))
self.assertFalse(is_dataclass(0))
self.assertFalse(is_dataclass(int))
self.assertFalse(is_dataclass(NotDataClass))
self.assertFalse(is_dataclass(NotDataClass()))

@dataclass
class C:
x: int

self.assertTrue(isdataclass(C))
self.assertTrue(isdataclass(C(0)))
@dataclass
class D:
d: C
e: int

c = C(10)
d = D(c, 4)

self.assertTrue(is_dataclass(C))
self.assertTrue(is_dataclass(c))
self.assertFalse(is_dataclass(c.x))
self.assertTrue(is_dataclass(d.d))
self.assertFalse(is_dataclass(d.e))

def test_helper_fields_with_class_instance(self):
# Check that we can call fields() on either a class or instance,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add dataclasses.is_dataclass(obj), which returns True if obj is a dataclass
or an instance of one.