diff --git a/CHANGES.md b/CHANGES.md index 577567111..4e9b12be0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,9 @@ when importing cloudpickle. ([issue #337](https://github.com/cloudpipe/cloudpickle/pull/337)) +- Fix a bug affecting subclasses of slotted classes. + ([issue #311](https://github.com/cloudpipe/cloudpickle/issues/311)) + 1.2.2 ===== diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index c48572323..17656a8d5 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -640,7 +640,7 @@ def save_dynamic_class(self, obj): # doc can't participate in a cycle with the original class. type_kwargs = {'__doc__': clsdict.pop('__doc__', None)} - if hasattr(obj, "__slots__"): + if "__slots__" in clsdict: type_kwargs['__slots__'] = obj.__slots__ # pickle string length optimization: member descriptors of obj are # created automatically from obj's __slots__ attribute, no need to diff --git a/cloudpickle/cloudpickle_fast.py b/cloudpickle/cloudpickle_fast.py index 1b6f6a38c..0964a36f6 100644 --- a/cloudpickle/cloudpickle_fast.py +++ b/cloudpickle/cloudpickle_fast.py @@ -68,7 +68,7 @@ def dumps(obj, protocol=None, buffer_callback=None): def _class_getnewargs(obj): type_kwargs = {} - if hasattr(obj, "__slots__"): + if "__slots__" in obj.__dict__: type_kwargs["__slots__"] = obj.__slots__ __dict__ = obj.__dict__.get('__dict__', None) @@ -143,7 +143,7 @@ def _class_getstate(obj): (registry, _, _, _) = abc._get_dump(obj) clsdict["_abc_impl"] = [subclass_weakref() for subclass_weakref in registry] - if hasattr(obj, "__slots__"): + if "__slots__" in clsdict: # pickle string length optimization: member descriptors of obj are # created automatically from obj's __slots__ attribute, no need to # save them in obj's state diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index dd425ec2e..a64bae68a 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -1842,6 +1842,17 @@ def __init__(self): with pytest.raises(AttributeError): obj.non_registered_attribute = 1 + class SubclassWithSlots(ClassWithSlots): + def __init__(self): + self.unregistered_attribute = 1 + + obj = SubclassWithSlots() + s = cloudpickle.dumps(obj, protocol=self.protocol) + del SubclassWithSlots + depickled_obj = cloudpickle.loads(s) + assert depickled_obj.unregistered_attribute == 1 + + @unittest.skipIf(not hasattr(types, "MappingProxyType"), "Old versions of Python do not have this type.") def test_mappingproxy(self):