Skip to content
25 changes: 13 additions & 12 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,9 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
# Remember all of the fields on our class (including bases). This
# also marks this class as being a dataclass.
setattr(cls, _FIELDS, fields)
# Store field names. Excludes pseudo-fields.
cls.__dataclass_field_names__ = tuple(f.name for f in fields.values()
if f._field_type is _FIELD)

# Was this class defined with an explicit __hash__? Note that if
# __eq__ is defined in this class, then python will automatically
Expand Down Expand Up @@ -1196,13 +1199,13 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
# the code instead of iterating over fields. But that can be a project for
# another day, if performance becomes an issue.
def _dataclass_getstate(self):
return [getattr(self, f.name) for f in fields(self)]
return [getattr(self, name) for name in self.__dataclass_field_names__]


def _dataclass_setstate(self, state):
for field, value in zip(fields(self), state):
for field_name, value in zip(self.__dataclass_field_names__, state):
# use setattr because dataclass may be frozen
object.__setattr__(self, field.name, value)
object.__setattr__(self, field_name, value)


def _get_slots(cls):
Expand Down Expand Up @@ -1285,7 +1288,7 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):

# Create a new dict for our new class.
cls_dict = dict(cls.__dict__)
field_names = tuple(f.name for f in fields(cls))
field_names = cls.__dataclass_field_names__
# Make sure slots don't overlap with those in base classes.
inherited_slots = set(
itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1]))
Expand Down Expand Up @@ -1377,8 +1380,6 @@ def fields(class_or_instance):
Accepts a dataclass or an instance of one. Tuple elements are of
type Field.
"""

# Might it be worth caching this, per class?
try:
fields = getattr(class_or_instance, _FIELDS)
except AttributeError:
Expand Down Expand Up @@ -1433,13 +1434,13 @@ def _asdict_inner(obj, dict_factory):
# dataclass instance: fast path for the common case
if dict_factory is dict:
return {
f.name: _asdict_inner(getattr(obj, f.name), dict)
for f in fields(obj)
name: _asdict_inner(getattr(obj, name), dict)
for name in obj_type.__dataclass_field_names__
}
else:
return dict_factory([
(f.name, _asdict_inner(getattr(obj, f.name), dict_factory))
for f in fields(obj)
(name, _asdict_inner(getattr(obj, name), dict_factory))
for name in obj_type.__dataclass_field_names__
])
# handle the builtin types first for speed; subclasses handled below
elif obj_type is list:
Expand Down Expand Up @@ -1522,8 +1523,8 @@ def _astuple_inner(obj, tuple_factory):
return obj
elif _is_dataclass_instance(obj):
return tuple_factory([
_astuple_inner(getattr(obj, f.name), tuple_factory)
for f in fields(obj)
_astuple_inner(getattr(obj, name), tuple_factory)
for name in obj.__dataclass_field_names__
])
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
# obj is a namedtuple. Recurse into it, but the returned
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve performance of :func:`dataclasses.asdict` up to 40% by caching the field names on dataclass classes.
Loading