Unverified Kaydet (Commit) 01d618c5 authored tarafından Eric V. Smith's avatar Eric V. Smith Kaydeden (comit) GitHub

bpo-33134: dataclasses: use function dispatch table for hash, instead of a…

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.
üst f96ddade
...@@ -585,14 +585,24 @@ def _set_new_attribute(cls, name, value): ...@@ -585,14 +585,24 @@ def _set_new_attribute(cls, name, value):
return False return False
# Decide if/how we're going to create a hash function. Key is # Decide if/how we're going to create a hash function. Key is
# (unsafe_hash, eq, frozen, does-hash-exist). Value is the action to # (unsafe_hash, eq, frozen, does-hash-exist). Value is the action to
# take. # take. The common case is to do nothing, so instead of providing a
# Actions: # function that is a no-op, use None to signify that.
# '': Do nothing.
# 'none': Set __hash__ to None. def _hash_set_none(cls, fields):
# 'add': Always add a generated __hash__function. return None
# 'exception': Raise an exception.
def _hash_add(cls, fields):
flds = [f for f in fields if (f.compare if f.hash is None else f.hash)]
return _hash_fn(flds)
def _hash_exception(cls, fields):
# Raise an exception.
raise TypeError(f'Cannot overwrite attribute __hash__ '
f'in class {cls.__name__}')
# #
# +-------------------------------------- unsafe_hash? # +-------------------------------------- unsafe_hash?
# | +------------------------------- eq? # | +------------------------------- eq?
...@@ -602,22 +612,22 @@ def _set_new_attribute(cls, name, value): ...@@ -602,22 +612,22 @@ def _set_new_attribute(cls, name, value):
# | | | | +------- action # | | | | +------- action
# | | | | | # | | | | |
# v v v v v # v v v v v
_hash_action = {(False, False, False, False): (''), _hash_action = {(False, False, False, False): None,
(False, False, False, True ): (''), (False, False, False, True ): None,
(False, False, True, False): (''), (False, False, True, False): None,
(False, False, True, True ): (''), (False, False, True, True ): None,
(False, True, False, False): ('none'), (False, True, False, False): _hash_set_none,
(False, True, False, True ): (''), (False, True, False, True ): None,
(False, True, True, False): ('add'), (False, True, True, False): _hash_add,
(False, True, True, True ): (''), (False, True, True, True ): None,
(True, False, False, False): ('add'), (True, False, False, False): _hash_add,
(True, False, False, True ): ('exception'), (True, False, False, True ): _hash_exception,
(True, False, True, False): ('add'), (True, False, True, False): _hash_add,
(True, False, True, True ): ('exception'), (True, False, True, True ): _hash_exception,
(True, True, False, False): ('add'), (True, True, False, False): _hash_add,
(True, True, False, True ): ('exception'), (True, True, False, True ): _hash_exception,
(True, True, True, False): ('add'), (True, True, True, False): _hash_add,
(True, True, True, True ): ('exception'), (True, True, True, True ): _hash_exception,
} }
# See https://bugs.python.org/issue32929#msg312829 for an if-statement # See https://bugs.python.org/issue32929#msg312829 for an if-statement
# version of this table. # version of this table.
...@@ -774,7 +784,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen): ...@@ -774,7 +784,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
'functools.total_ordering') 'functools.total_ordering')
if frozen: if frozen:
# XXX: Which fields are frozen? InitVar? ClassVar? hashed-only?
for fn in _frozen_get_del_attr(cls, field_list): for fn in _frozen_get_del_attr(cls, field_list):
if _set_new_attribute(cls, fn.__name__, fn): if _set_new_attribute(cls, fn.__name__, fn):
raise TypeError(f'Cannot overwrite attribute {fn.__name__} ' raise TypeError(f'Cannot overwrite attribute {fn.__name__} '
...@@ -785,23 +794,10 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen): ...@@ -785,23 +794,10 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
bool(eq), bool(eq),
bool(frozen), bool(frozen),
has_explicit_hash] has_explicit_hash]
if hash_action:
# No need to call _set_new_attribute here, since we already know if # No need to call _set_new_attribute here, since by the time
# we're overwriting a __hash__ or not. # we're here the overwriting is unconditional.
if hash_action == '': cls.__hash__ = hash_action(cls, field_list)
# Do nothing.
pass
elif hash_action == 'none':
cls.__hash__ = None
elif hash_action == 'add':
flds = [f for f in field_list if (f.compare if f.hash is None else f.hash)]
cls.__hash__ = _hash_fn(flds)
elif hash_action == 'exception':
# Raise an exception.
raise TypeError(f'Cannot overwrite attribute __hash__ '
f'in class {cls.__name__}')
else:
assert False, f"can't get here: {hash_action}"
if not getattr(cls, '__doc__'): if not getattr(cls, '__doc__'):
# Create a class doc-string. # Create a class doc-string.
......
When computing dataclass's __hash__, use the lookup table to contain the
function which returns the __hash__ value. This is an improvement over
looking up a string, and then testing that string to see what to do.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment