Skip to content

Commit

Permalink
Change __getstate__ and __setstate__ to use a dict (#1004)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikola Dipanov committed Aug 17, 2022
1 parent 5ecc397 commit 6dd9c31
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 3 deletions.
7 changes: 4 additions & 3 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ def slots_getstate(self):
"""
Automatically created by attrs.
"""
return tuple(getattr(self, name) for name in state_attr_names)
return {name: getattr(self, name) for name in state_attr_names}

hash_caching_enabled = self._cache_hash

Expand All @@ -931,8 +931,9 @@ def slots_setstate(self, state):
Automatically created by attrs.
"""
__bound_setattr = _obj_setattr.__get__(self)
for name, value in zip(state_attr_names, state):
__bound_setattr(name, value)
for name in state_attr_names:
if name in state:
__bound_setattr(name, state[name])

# The hash code cache is not included when the object is
# serialized, but it still needs to be initialized to None to
Expand Down
56 changes: 56 additions & 0 deletions tests/test_slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import types
import weakref

from unittest import mock

import pytest

import attr
Expand Down Expand Up @@ -743,3 +745,57 @@ def f(self):

assert B(11).f == 121
assert B(17).f == 289


@attr.s(slots=True)
class A:
x = attr.ib()
b = attr.ib()
c = attr.ib()


@pytest.mark.parametrize("cls", [A])
def test_slots_unpickle_after_attr_removed(cls):
"""
We don't assign attributes we don't have anymore if the class has removed it.
"""
a = cls(1, 2, 3)
a_pickled = pickle.dumps(a)
a_unpickled = pickle.loads(a_pickled)
assert a_unpickled == a

@attr.s(slots=True)
class NEW_A:
x = attr.ib()
c = attr.ib()

with mock.patch(f"{__name__}.A", NEW_A):
new_a = pickle.loads(a_pickled)
assert new_a.x == 1
assert new_a.c == 3
assert not hasattr(new_a, "b")


@pytest.mark.parametrize("cls", [A])
def test_slots_unpickle_after_attr_added(cls):
"""
We don't assign attribute we haven't had before if the class has one added.
"""
a = cls(1, 2, 3)
a_pickled = pickle.dumps(a)
a_unpickled = pickle.loads(a_pickled)
assert a_unpickled == a

@attr.s(slots=True)
class NEW_A:
x = attr.ib()
b = attr.ib()
d = attr.ib()
c = attr.ib()

with mock.patch(f"{__name__}.A", NEW_A):
new_a = pickle.loads(a_pickled)
assert new_a.x == 1
assert new_a.b == 2
assert new_a.c == 3
assert not hasattr(new_a, "d")

0 comments on commit 6dd9c31

Please sign in to comment.