Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change __getstate__ and __setstate__ to use a dict (#1004) #1009

Merged
merged 1 commit into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
57 changes: 57 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,58 @@ 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")