Skip to content

Commit

Permalink
BUG: Fix bug pickling namedtuple.
Browse files Browse the repository at this point in the history
Fixes a crash when trying to set `__dict__` onto a type when `__dict__`
is a property.
  • Loading branch information
Scott Sanderson committed Aug 30, 2017
1 parent 16cacb0 commit 8675793
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 15 deletions.
26 changes: 12 additions & 14 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,18 @@ def save_dynamic_class(self, obj):
from global modules.
"""
clsdict = dict(obj.__dict__) # copy dict proxy to a dict
if not isinstance(clsdict.get('__dict__', None), property):
# don't extract dict that are properties
clsdict.pop('__dict__', None)
clsdict.pop('__weakref__', None)
clsdict.pop('__weakref__', None)

# hack as __new__ is stored differently in the __dict__
new_override = clsdict.get('__new__', None)
if new_override:
clsdict['__new__'] = obj.__new__
# On PyPy, __doc__ is a readonly attribute, so we need to include it in
# the initial skeleton class. This is safe because we know that the
# doc can't participate in a cycle with the original class.
type_kwargs = {'__doc__': clsdict.pop('__doc__', 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.
__dict__ = clsdict.pop('__dict__', None)
if isinstance(__dict__, property):
type_kwargs['__dict__'] = __dict__

save = self.save
write = self.write
Expand All @@ -439,17 +442,12 @@ def save_dynamic_class(self, obj):
# Mark the start of the args for the rehydration function.
write(pickle.MARK)

# On PyPy, __doc__ is a readonly attribute, so we need to include it in
# the initial skeleton class. This is safe because we know that the
# doc can't participate in a cycle with the original class.
doc_dict = {'__doc__': clsdict.pop('__doc__', None)}

# Create and memoize an empty class with obj's name and bases.
save(type(obj))
save((
obj.__name__,
obj.__bases__,
doc_dict,
type_kwargs,
))
write(pickle.REDUCE)
self.memoize(obj)
Expand Down
11 changes: 10 additions & 1 deletion tests/cloudpickle_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import division

import abc

import collections
import base64
import functools
import imp
Expand Down Expand Up @@ -701,5 +701,14 @@ def test_function_module_name(self):
func = lambda x: x
self.assertEqual(pickle_depickle(func).__module__, func.__module__)

def test_namedtuple(self):
MyTuple = collections.namedtuple('MyTuple', ['a', 'b', 'c'])

t = MyTuple(1, 2, 3)
depickled_t = pickle_depickle(t)

self.assertEqual((depickled_t.a, depickled_t.b, depickled_t.c), (1, 2, 3))
self.assertEqual(vars(t), vars(depickled_t))

if __name__ == '__main__':
unittest.main()

0 comments on commit 8675793

Please sign in to comment.