Skip to content

Commit 28070bb

Browse files
Scott Sandersonrgbkrk
authored andcommitted
BUG: Fix bug pickling namedtuple.
Fixes a crash when trying to set `__dict__` onto a type when `__dict__` is a property.
1 parent 16cacb0 commit 28070bb

File tree

2 files changed

+22
-15
lines changed

2 files changed

+22
-15
lines changed

cloudpickle/cloudpickle.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -408,15 +408,18 @@ def save_dynamic_class(self, obj):
408408
from global modules.
409409
"""
410410
clsdict = dict(obj.__dict__) # copy dict proxy to a dict
411-
if not isinstance(clsdict.get('__dict__', None), property):
412-
# don't extract dict that are properties
413-
clsdict.pop('__dict__', None)
414-
clsdict.pop('__weakref__', None)
411+
clsdict.pop('__weakref__', None)
415412

416-
# hack as __new__ is stored differently in the __dict__
417-
new_override = clsdict.get('__new__', None)
418-
if new_override:
419-
clsdict['__new__'] = obj.__new__
413+
# On PyPy, __doc__ is a readonly attribute, so we need to include it in
414+
# the initial skeleton class. This is safe because we know that the
415+
# doc can't participate in a cycle with the original class.
416+
type_kwargs = {'__doc__': clsdict.pop('__doc__', None)}
417+
418+
# If type overrides __dict__ as a property, include it in the type kwargs.
419+
# In Python 2, we can't set this attribute after construction.
420+
__dict__ = clsdict.pop('__dict__', None)
421+
if isinstance(__dict__, property):
422+
type_kwargs['__dict__'] = __dict__
420423

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

442-
# On PyPy, __doc__ is a readonly attribute, so we need to include it in
443-
# the initial skeleton class. This is safe because we know that the
444-
# doc can't participate in a cycle with the original class.
445-
doc_dict = {'__doc__': clsdict.pop('__doc__', None)}
446-
447445
# Create and memoize an empty class with obj's name and bases.
448446
save(type(obj))
449447
save((
450448
obj.__name__,
451449
obj.__bases__,
452-
doc_dict,
450+
type_kwargs,
453451
))
454452
write(pickle.REDUCE)
455453
self.memoize(obj)

tests/cloudpickle_test.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import division
22

33
import abc
4-
4+
import collections
55
import base64
66
import functools
77
import imp
@@ -701,5 +701,14 @@ def test_function_module_name(self):
701701
func = lambda x: x
702702
self.assertEqual(pickle_depickle(func).__module__, func.__module__)
703703

704+
def test_namedtuple(self):
705+
MyTuple = collections.namedtuple('MyTuple', ['a', 'b', 'c'])
706+
707+
t = MyTuple(1, 2, 3)
708+
depickled_t = pickle_depickle(t)
709+
710+
self.assertEqual((depickled_t.a, depickled_t.b, depickled_t.c), (1, 2, 3))
711+
self.assertEqual(vars(t), vars(depickled_t))
712+
704713
if __name__ == '__main__':
705714
unittest.main()

0 commit comments

Comments
 (0)