bpo-32499: Add dataclasses.is_dataclass(obj), which returns True if obj is a dataclass or an instance of one. (#5113)
This commit is contained in:
parent
3cd7c6e6eb
commit
e7ba013d87
|
@ -16,6 +16,7 @@ __all__ = ['dataclass',
|
||||||
'astuple',
|
'astuple',
|
||||||
'make_dataclass',
|
'make_dataclass',
|
||||||
'replace',
|
'replace',
|
||||||
|
'is_dataclass',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Raised when an attempt is made to modify a frozen class.
|
# Raised when an attempt is made to modify a frozen class.
|
||||||
|
@ -615,11 +616,17 @@ def fields(class_or_instance):
|
||||||
return tuple(f for f in fields.values() if f._field_type is _FIELD)
|
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."""
|
"""Returns True if obj is an instance of a dataclass."""
|
||||||
return not isinstance(obj, type) and hasattr(obj, _MARKER)
|
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):
|
def asdict(obj, *, dict_factory=dict):
|
||||||
"""Return the fields of a dataclass instance as a new dictionary mapping
|
"""Return the fields of a dataclass instance as a new dictionary mapping
|
||||||
field names to field values.
|
field names to field values.
|
||||||
|
@ -639,12 +646,12 @@ def asdict(obj, *, dict_factory=dict):
|
||||||
dataclass instances. This will also look into built-in containers:
|
dataclass instances. This will also look into built-in containers:
|
||||||
tuples, lists, and dicts.
|
tuples, lists, and dicts.
|
||||||
"""
|
"""
|
||||||
if not _isdataclass(obj):
|
if not _is_dataclass_instance(obj):
|
||||||
raise TypeError("asdict() should be called on dataclass instances")
|
raise TypeError("asdict() should be called on dataclass instances")
|
||||||
return _asdict_inner(obj, dict_factory)
|
return _asdict_inner(obj, dict_factory)
|
||||||
|
|
||||||
def _asdict_inner(obj, dict_factory):
|
def _asdict_inner(obj, dict_factory):
|
||||||
if _isdataclass(obj):
|
if _is_dataclass_instance(obj):
|
||||||
result = []
|
result = []
|
||||||
for f in fields(obj):
|
for f in fields(obj):
|
||||||
value = _asdict_inner(getattr(obj, f.name), dict_factory)
|
value = _asdict_inner(getattr(obj, f.name), dict_factory)
|
||||||
|
@ -678,12 +685,12 @@ def astuple(obj, *, tuple_factory=tuple):
|
||||||
tuples, lists, and dicts.
|
tuples, lists, and dicts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not _isdataclass(obj):
|
if not _is_dataclass_instance(obj):
|
||||||
raise TypeError("astuple() should be called on dataclass instances")
|
raise TypeError("astuple() should be called on dataclass instances")
|
||||||
return _astuple_inner(obj, tuple_factory)
|
return _astuple_inner(obj, tuple_factory)
|
||||||
|
|
||||||
def _astuple_inner(obj, tuple_factory):
|
def _astuple_inner(obj, tuple_factory):
|
||||||
if _isdataclass(obj):
|
if _is_dataclass_instance(obj):
|
||||||
result = []
|
result = []
|
||||||
for f in fields(obj):
|
for f in fields(obj):
|
||||||
value = _astuple_inner(getattr(obj, f.name), tuple_factory)
|
value = _astuple_inner(getattr(obj, f.name), tuple_factory)
|
||||||
|
@ -751,7 +758,7 @@ def replace(obj, **changes):
|
||||||
# We're going to mutate 'changes', but that's okay because it's a new
|
# We're going to mutate 'changes', but that's okay because it's a new
|
||||||
# dict, even if called with 'replace(obj, **my_changes)'.
|
# 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")
|
raise TypeError("replace() should be called on dataclass instances")
|
||||||
|
|
||||||
# It's an error to have init=False fields in 'changes'.
|
# It's an error to have init=False fields in 'changes'.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from dataclasses import (
|
from dataclasses import (
|
||||||
dataclass, field, FrozenInstanceError, fields, asdict, astuple,
|
dataclass, field, FrozenInstanceError, fields, asdict, astuple,
|
||||||
make_dataclass, replace, InitVar, Field, MISSING
|
make_dataclass, replace, InitVar, Field, MISSING, is_dataclass,
|
||||||
)
|
)
|
||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
|
@ -1365,27 +1365,32 @@ class TestCase(unittest.TestCase):
|
||||||
|
|
||||||
self.assertIs(C().x, int)
|
self.assertIs(C().x, int)
|
||||||
|
|
||||||
def test_isdataclass(self):
|
def test_is_dataclass(self):
|
||||||
# There is no isdataclass() helper any more, but the PEP
|
class NotDataClass:
|
||||||
# describes how to write it, so make sure that works. Note
|
pass
|
||||||
# that this version returns True for both classes and
|
|
||||||
# instances.
|
|
||||||
def isdataclass(obj):
|
|
||||||
try:
|
|
||||||
fields(obj)
|
|
||||||
return True
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.assertFalse(isdataclass(0))
|
self.assertFalse(is_dataclass(0))
|
||||||
self.assertFalse(isdataclass(int))
|
self.assertFalse(is_dataclass(int))
|
||||||
|
self.assertFalse(is_dataclass(NotDataClass))
|
||||||
|
self.assertFalse(is_dataclass(NotDataClass()))
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class C:
|
class C:
|
||||||
x: int
|
x: int
|
||||||
|
|
||||||
self.assertTrue(isdataclass(C))
|
@dataclass
|
||||||
self.assertTrue(isdataclass(C(0)))
|
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):
|
def test_helper_fields_with_class_instance(self):
|
||||||
# Check that we can call fields() on either a class or instance,
|
# Check that we can call fields() on either a class or instance,
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add dataclasses.is_dataclass(obj), which returns True if obj is a dataclass
|
||||||
|
or an instance of one.
|
Loading…
Reference in New Issue