Skip to content

Commit 01d618c

Browse files
authored
bpo-33134: dataclasses: use function dispatch table for hash, instead of a string lookup which then is tested with if tests. (GH-6222)
* Change _hash_action to be a function table lookup, instead of a list of strings which is then tested with if statements.
1 parent f96ddad commit 01d618c

File tree

2 files changed

+39
-40
lines changed

2 files changed

+39
-40
lines changed

Lib/dataclasses.py

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -585,14 +585,24 @@ def _set_new_attribute(cls, name, value):
585585
return False
586586

587587

588+
588589
# Decide if/how we're going to create a hash function. Key is
589590
# (unsafe_hash, eq, frozen, does-hash-exist). Value is the action to
590-
# take.
591-
# Actions:
592-
# '': Do nothing.
593-
# 'none': Set __hash__ to None.
594-
# 'add': Always add a generated __hash__function.
595-
# 'exception': Raise an exception.
591+
# take. The common case is to do nothing, so instead of providing a
592+
# function that is a no-op, use None to signify that.
593+
594+
def _hash_set_none(cls, fields):
595+
return None
596+
597+
def _hash_add(cls, fields):
598+
flds = [f for f in fields if (f.compare if f.hash is None else f.hash)]
599+
return _hash_fn(flds)
600+
601+
def _hash_exception(cls, fields):
602+
# Raise an exception.
603+
raise TypeError(f'Cannot overwrite attribute __hash__ '
604+
f'in class {cls.__name__}')
605+
596606
#
597607
# +-------------------------------------- unsafe_hash?
598608
# | +------------------------------- eq?
@@ -602,22 +612,22 @@ def _set_new_attribute(cls, name, value):
602612
# | | | | +------- action
603613
# | | | | |
604614
# v v v v v
605-
_hash_action = {(False, False, False, False): (''),
606-
(False, False, False, True ): (''),
607-
(False, False, True, False): (''),
608-
(False, False, True, True ): (''),
609-
(False, True, False, False): ('none'),
610-
(False, True, False, True ): (''),
611-
(False, True, True, False): ('add'),
612-
(False, True, True, True ): (''),
613-
(True, False, False, False): ('add'),
614-
(True, False, False, True ): ('exception'),
615-
(True, False, True, False): ('add'),
616-
(True, False, True, True ): ('exception'),
617-
(True, True, False, False): ('add'),
618-
(True, True, False, True ): ('exception'),
619-
(True, True, True, False): ('add'),
620-
(True, True, True, True ): ('exception'),
615+
_hash_action = {(False, False, False, False): None,
616+
(False, False, False, True ): None,
617+
(False, False, True, False): None,
618+
(False, False, True, True ): None,
619+
(False, True, False, False): _hash_set_none,
620+
(False, True, False, True ): None,
621+
(False, True, True, False): _hash_add,
622+
(False, True, True, True ): None,
623+
(True, False, False, False): _hash_add,
624+
(True, False, False, True ): _hash_exception,
625+
(True, False, True, False): _hash_add,
626+
(True, False, True, True ): _hash_exception,
627+
(True, True, False, False): _hash_add,
628+
(True, True, False, True ): _hash_exception,
629+
(True, True, True, False): _hash_add,
630+
(True, True, True, True ): _hash_exception,
621631
}
622632
# See https://bugs.python.org/issue32929#msg312829 for an if-statement
623633
# version of this table.
@@ -774,7 +784,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
774784
'functools.total_ordering')
775785

776786
if frozen:
777-
# XXX: Which fields are frozen? InitVar? ClassVar? hashed-only?
778787
for fn in _frozen_get_del_attr(cls, field_list):
779788
if _set_new_attribute(cls, fn.__name__, fn):
780789
raise TypeError(f'Cannot overwrite attribute {fn.__name__} '
@@ -785,23 +794,10 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
785794
bool(eq),
786795
bool(frozen),
787796
has_explicit_hash]
788-
789-
# No need to call _set_new_attribute here, since we already know if
790-
# we're overwriting a __hash__ or not.
791-
if hash_action == '':
792-
# Do nothing.
793-
pass
794-
elif hash_action == 'none':
795-
cls.__hash__ = None
796-
elif hash_action == 'add':
797-
flds = [f for f in field_list if (f.compare if f.hash is None else f.hash)]
798-
cls.__hash__ = _hash_fn(flds)
799-
elif hash_action == 'exception':
800-
# Raise an exception.
801-
raise TypeError(f'Cannot overwrite attribute __hash__ '
802-
f'in class {cls.__name__}')
803-
else:
804-
assert False, f"can't get here: {hash_action}"
797+
if hash_action:
798+
# No need to call _set_new_attribute here, since by the time
799+
# we're here the overwriting is unconditional.
800+
cls.__hash__ = hash_action(cls, field_list)
805801

806802
if not getattr(cls, '__doc__'):
807803
# Create a class doc-string.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
When computing dataclass's __hash__, use the lookup table to contain the
2+
function which returns the __hash__ value. This is an improvement over
3+
looking up a string, and then testing that string to see what to do.

0 commit comments

Comments
 (0)