Skip to content

Commit

Permalink
FIX pass the __dict__ item of a class __dict__
Browse files Browse the repository at this point in the history
  • Loading branch information
pierreglaser committed Jun 13, 2019
1 parent d884a01 commit d967827
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 3 deletions.
19 changes: 16 additions & 3 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,23 @@ def save_dynamic_class(self, obj):
for k in obj.__slots__:
clsdict.pop(k, None)

# If type overrides __dict__ as a property, include it in the type
# kwargs. In Python 2, we can't set this attribute after construction.
# A class __dict__ is part of the class state. At unpickling time, it
# must be *initialized* (in an empty state) during class creation and
# updated during class re-hydratation.
# However, a class __dict__ is read-only, and does not support direct
# item assignement. Instead, way to update a class __dict__ is to call
# setattr(k, v) on the underlying class, which has the same effect.
# There is one corner case: if the __dict__ class has itself a
# "__dict__" key (this means that the class likely overrides the
# __dict__ property of its instances), setattr("__dict__", v) will try
# to modify the read-only class __dict__ instead, and fail. As a
# result, if it exists, the class __dict__ must contain its __dict__
# item when it is initialized and fed to the class reconstructor.
__dict__ = clsdict.pop('__dict__', None)
if isinstance(__dict__, property):
if __dict__ is not None:
# Native pickle memoization of dict objects prevents us from
# reference cycles even if __dict__ is now saved before obj is
# memoized.
type_kwargs['__dict__'] = __dict__

save = self.save
Expand Down
23 changes: 23 additions & 0 deletions tests/cloudpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,29 @@ def __getattr__(self, name):
with pytest.raises(pickle.PicklingError, match='recursion'):
cloudpickle.dumps(a)

def test___dict__attribute_not_dropped_during_pickling(self):
# Test https://github.com/cloudpipe/cloudpickle/issues/282. cloudpickle
# used to drop __dict__ attributes of classes at pickling time.
pickle_filename = os.path.join(self.tmpdir, 'class_with___dict__.pkl')
_dict = {'some_attribute': 1}
class A:
__dict__ = _dict
a = A()
self.assertEqual(a.__dict__, _dict)

with open(pickle_filename, "wb") as f:
cloudpickle.dump(a, f, protocol=self.protocol)

# Depickle the class in a new python session to make sure the class is
# fully-recreated, and not looked-up in existing cloudpickle
# class-tracking constructs.
child_process_script = """
import pickle
with open("{filename}", "rb") as f:
depickled_a = pickle.load(f)
assert depickled_a.__dict__ == {_dict}, depickled_a.__dict__
""".format(filename=pickle_filename, _dict=_dict)
assert_run_python_script(textwrap.dedent(child_process_script))

class Protocol2CloudPickleTest(CloudPickleTest):

Expand Down

0 comments on commit d967827

Please sign in to comment.