Skip to content

Commit

Permalink
Implement __reduce__ with cache_hash
Browse files Browse the repository at this point in the history
This fixes GH issue python-attrs#613 and python-attrs#494. It turns out that the hash
cache-clearing implementation for non-slots classes was flawed and never
quite worked properly. This switches away from using __setstate__ and
instead adds a custom __reduce__ that removes the cached hash value from the
default serialized output.
  • Loading branch information
pganssle committed Jan 14, 2020
1 parent 9b5e988 commit 20c9366
Showing 1 changed file with 18 additions and 28 deletions.
46 changes: 18 additions & 28 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,17 @@ def _frozen_delattrs(self, name):
raise FrozenInstanceError()


def _cache_hash_reduce(self):
obj_reduce = object.__reduce__(self)
if len(obj_reduce) > 2:
state = obj_reduce[2]

if isinstance(state, dict) and _hash_cache_field in state:
state[_hash_cache_field] = None

return obj_reduce


class _ClassBuilder(object):
"""
Iteratively build *one* class.
Expand Down Expand Up @@ -483,6 +494,13 @@ def __init__(
self._cls_dict["__setattr__"] = _frozen_setattrs
self._cls_dict["__delattr__"] = _frozen_delattrs

if (
cache_hash
and cls.__reduce__ is object.__reduce__
and cls.__reduce_ex__ is object.__reduce_ex__
):
self._cls_dict["__reduce__"] = _cache_hash_reduce

def __repr__(self):
return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__)

Expand Down Expand Up @@ -523,34 +541,6 @@ def _patch_original_class(self):
for name, value in self._cls_dict.items():
setattr(cls, name, value)

# Attach __setstate__. This is necessary to clear the hash code
# cache on deserialization. See issue
# https://github.com/python-attrs/attrs/issues/482 .
# Note that this code only handles setstate for dict classes.
# For slotted classes, see similar code in _create_slots_class .
if self._cache_hash:
existing_set_state_method = getattr(cls, "__setstate__", None)
if existing_set_state_method:
raise NotImplementedError(
"Currently you cannot use hash caching if "
"you specify your own __setstate__ method."
"See https://github.com/python-attrs/attrs/issues/494 ."
)

# Clears the cached hash state on serialization; for frozen
# classes we need to bypass the class's setattr method.
if self._frozen:

def cache_hash_set_state(chss_self, _):
object.__setattr__(chss_self, _hash_cache_field, None)

else:

def cache_hash_set_state(chss_self, _):
setattr(chss_self, _hash_cache_field, None)

cls.__setstate__ = cache_hash_set_state

return cls

def _create_slots_class(self):
Expand Down

0 comments on commit 20c9366

Please sign in to comment.