Fix and accelerate generated __hash__ methods (#296)
* Fix and accelerate generated __hash__ methods We now create a method like we do for __init__ and hash also the class of the instance. * Add/fix news fragments * Hash random numbers instead of the actual class * Even simpler: let's hash the unique filename Or rather it's hash, because hashing ints is very fast.
This commit is contained in:
parent
ef9a062022
commit
defa6b6f40
|
@ -0,0 +1,4 @@
|
||||||
|
Generated ``__hash__`` methods now hash the class type along with the attribute values.
|
||||||
|
Until now the hashes of two classes with the same values were identical which was a bug.
|
||||||
|
|
||||||
|
The generated method is also *much* faster now.
|
|
@ -0,0 +1,4 @@
|
||||||
|
Generated ``__hash__`` methods now hash the class type along with the attribute values.
|
||||||
|
Until now the hashes of two classes with the same values were identical which was a bug.
|
||||||
|
|
||||||
|
The generated method is also *much* faster now.
|
|
@ -0,0 +1,4 @@
|
||||||
|
Generated ``__hash__`` methods now hash the class type along with the attribute values.
|
||||||
|
Until now the hashes of two classes with the same values were identical which was a bug.
|
||||||
|
|
||||||
|
The generated method is also *much* faster now.
|
|
@ -701,13 +701,37 @@ def _make_hash(attrs):
|
||||||
if a.hash is True or (a.hash is None and a.cmp is True)
|
if a.hash is True or (a.hash is None and a.cmp is True)
|
||||||
)
|
)
|
||||||
|
|
||||||
def hash_(self):
|
# We cache the generated init methods for the same kinds of attributes.
|
||||||
"""
|
sha1 = hashlib.sha1()
|
||||||
Automatically created by attrs.
|
sha1.update(repr(attrs).encode("utf-8"))
|
||||||
"""
|
unique_filename = "<attrs generated hash %s>" % (sha1.hexdigest(),)
|
||||||
return hash(_attrs_to_tuple(self, attrs))
|
type_hash = hash(unique_filename)
|
||||||
|
lines = [
|
||||||
|
"def __hash__(self):",
|
||||||
|
" return hash((",
|
||||||
|
" %d," % (type_hash,),
|
||||||
|
]
|
||||||
|
for a in attrs:
|
||||||
|
lines.append(" self.%s," % (a.name))
|
||||||
|
|
||||||
return hash_
|
lines.append(" ))")
|
||||||
|
|
||||||
|
script = "\n".join(lines)
|
||||||
|
globs = {}
|
||||||
|
locs = {}
|
||||||
|
bytecode = compile(script, unique_filename, "exec")
|
||||||
|
eval(bytecode, globs, locs)
|
||||||
|
|
||||||
|
# In order of debuggers like PDB being able to step through the code,
|
||||||
|
# we add a fake linecache entry.
|
||||||
|
linecache.cache[unique_filename] = (
|
||||||
|
len(script),
|
||||||
|
None,
|
||||||
|
script.splitlines(True),
|
||||||
|
unique_filename,
|
||||||
|
)
|
||||||
|
|
||||||
|
return locs["__hash__"]
|
||||||
|
|
||||||
|
|
||||||
def _add_hash(cls, attrs):
|
def _add_hash(cls, attrs):
|
||||||
|
@ -858,7 +882,7 @@ def _make_init(attrs, post_init, frozen):
|
||||||
sha1.hexdigest()
|
sha1.hexdigest()
|
||||||
)
|
)
|
||||||
|
|
||||||
script, globs = _attrs_to_script(
|
script, globs = _attrs_to_init_script(
|
||||||
attrs,
|
attrs,
|
||||||
frozen,
|
frozen,
|
||||||
post_init,
|
post_init,
|
||||||
|
@ -875,7 +899,6 @@ def _make_init(attrs, post_init, frozen):
|
||||||
# immutability.
|
# immutability.
|
||||||
globs["_cached_setattr"] = _obj_setattr
|
globs["_cached_setattr"] = _obj_setattr
|
||||||
eval(bytecode, globs, locs)
|
eval(bytecode, globs, locs)
|
||||||
init = locs["__init__"]
|
|
||||||
|
|
||||||
# In order of debuggers like PDB being able to step through the code,
|
# In order of debuggers like PDB being able to step through the code,
|
||||||
# we add a fake linecache entry.
|
# we add a fake linecache entry.
|
||||||
|
@ -883,10 +906,10 @@ def _make_init(attrs, post_init, frozen):
|
||||||
len(script),
|
len(script),
|
||||||
None,
|
None,
|
||||||
script.splitlines(True),
|
script.splitlines(True),
|
||||||
unique_filename
|
unique_filename,
|
||||||
)
|
)
|
||||||
|
|
||||||
return init
|
return locs["__init__"]
|
||||||
|
|
||||||
|
|
||||||
def _add_init(cls, frozen):
|
def _add_init(cls, frozen):
|
||||||
|
@ -946,7 +969,7 @@ def validate(inst):
|
||||||
v(inst, a, getattr(inst, a.name))
|
v(inst, a, getattr(inst, a.name))
|
||||||
|
|
||||||
|
|
||||||
def _attrs_to_script(attrs, frozen, post_init):
|
def _attrs_to_init_script(attrs, frozen, post_init):
|
||||||
"""
|
"""
|
||||||
Return a script of an initializer for *attrs* and a dict of globals.
|
Return a script of an initializer for *attrs* and a dict of globals.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue