Skip to content

Commit 07a4ee3

Browse files
committed
Improve performance of dataclasses by caching dataclass field names
1 parent c779f23 commit 07a4ee3

File tree

1 file changed

+20
-12
lines changed

1 file changed

+20
-12
lines changed

Lib/dataclasses.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ def __repr__(self):
207207
# The name of an attribute on the class where we store the Field
208208
# objects. Also used to check if a class is a Data Class.
209209
_FIELDS = '__dataclass_fields__'
210+
# The name of an attribute on the class where we store the field names
211+
_FIELD_NAMES = '__dataclass_field_names__'
210212

211213
# The name of an attribute on the class that stores the parameters to
212214
# @dataclass.
@@ -1052,6 +1054,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
10521054
# Remember all of the fields on our class (including bases). This
10531055
# also marks this class as being a dataclass.
10541056
setattr(cls, _FIELDS, fields)
1057+
setattr(cls, _FIELD_NAMES, tuple(f.name for f in fields.values() if f._field_type is _FIELD))
10551058

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

12011204

12021205
def _dataclass_setstate(self, state):
1203-
for field, value in zip(fields(self), state):
1206+
for field_name, value in zip(_field_names(self), state):
12041207
# use setattr because dataclass may be frozen
1205-
object.__setattr__(self, field.name, value)
1208+
object.__setattr__(self, field_name, value)
12061209

12071210

12081211
def _get_slots(cls):
@@ -1285,7 +1288,7 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
12851288

12861289
# Create a new dict for our new class.
12871290
cls_dict = dict(cls.__dict__)
1288-
field_names = tuple(f.name for f in fields(cls))
1291+
field_names = _field_names(cls)
12891292
# Make sure slots don't overlap with those in base classes.
12901293
inherited_slots = set(
12911294
itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1]))
@@ -1377,8 +1380,6 @@ def fields(class_or_instance):
13771380
Accepts a dataclass or an instance of one. Tuple elements are of
13781381
type Field.
13791382
"""
1380-
1381-
# Might it be worth caching this, per class?
13821383
try:
13831384
fields = getattr(class_or_instance, _FIELDS)
13841385
except AttributeError:
@@ -1388,6 +1389,13 @@ def fields(class_or_instance):
13881389
# order, so the order of the tuple is as the fields were defined.
13891390
return tuple(f for f in fields.values() if f._field_type is _FIELD)
13901391

1392+
def _field_names(class_or_instance):
1393+
"""Return a tuple describing the field names of this dataclass.
1394+
1395+
Accepts a dataclass or an instance of one. Excludes pseudo-fields
1396+
"""
1397+
1398+
return getattr(class_or_instance, _FIELD_NAMES)
13911399

13921400
def _is_dataclass_instance(obj):
13931401
"""Returns True if obj is an instance of a dataclass."""
@@ -1433,13 +1441,13 @@ def _asdict_inner(obj, dict_factory):
14331441
# dataclass instance: fast path for the common case
14341442
if dict_factory is dict:
14351443
return {
1436-
f.name: _asdict_inner(getattr(obj, f.name), dict)
1437-
for f in fields(obj)
1444+
name: _asdict_inner(getattr(obj, name), dict)
1445+
for name in _field_names(obj_type)
14381446
}
14391447
else:
14401448
return dict_factory([
1441-
(f.name, _asdict_inner(getattr(obj, f.name), dict_factory))
1442-
for f in fields(obj)
1449+
(name, _asdict_inner(getattr(obj, name), dict_factory))
1450+
for name in _field_names(obj_type)
14431451
])
14441452
# handle the builtin types first for speed; subclasses handled below
14451453
elif obj_type is list:
@@ -1522,8 +1530,8 @@ def _astuple_inner(obj, tuple_factory):
15221530
return obj
15231531
elif _is_dataclass_instance(obj):
15241532
return tuple_factory([
1525-
_astuple_inner(getattr(obj, f.name), tuple_factory)
1526-
for f in fields(obj)
1533+
_astuple_inner(getattr(obj, name), tuple_factory)
1534+
for name in _field_names(obj)
15271535
])
15281536
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
15291537
# obj is a namedtuple. Recurse into it, but the returned

0 commit comments

Comments
 (0)