Skip to content
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
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
- Fix a bug affecting subclasses of slotted classes.
([issue #311](https://github.com/cloudpipe/cloudpickle/issues/311))

- Dont pickle the abc cache of dynamically defined classes for Python 3.6-
(This was already the case for python3.7+)
([issue #302](https://github.com/cloudpipe/cloudpickle/issues/302))

1.2.2
=====

Expand Down
29 changes: 21 additions & 8 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"""
from __future__ import print_function

import abc
import dis
from functools import partial
import io
Expand Down Expand Up @@ -626,14 +627,26 @@ def save_dynamic_class(self, obj):
clsdict = _extract_class_dict(obj)
clsdict.pop('__weakref__', None)

# For ABCMeta in python3.7+, remove _abc_impl as it is not picklable.
# This is a fix which breaks the cache but this only makes the first
# calls to issubclass slower.
if "_abc_impl" in clsdict:
import abc
(registry, _, _, _) = abc._get_dump(obj)
clsdict["_abc_impl"] = [subclass_weakref()
for subclass_weakref in registry]
if issubclass(type(obj), abc.ABCMeta):
# If obj is an instance of an ABCMeta subclass, dont pickle the
# cache/negative caches populated during isinstance/issubclass
# checks, but pickle the list of registered subclasses of obj.
clsdict.pop('_abc_cache', None)
clsdict.pop('_abc_negative_cache', None)
clsdict.pop('_abc_negative_cache_version', None)
registry = clsdict.pop('_abc_registry', None)
if registry is None:
# in Python3.7+, the abc caches and registered subclasses of a
# class are bundled into the single _abc_impl attribute
clsdict.pop('_abc_impl', None)
(registry, _, _, _) = abc._get_dump(obj)

clsdict["_abc_impl"] = [subclass_weakref()
for subclass_weakref in registry]
else:
# In the above if clause, registry is a set of weakrefs -- in
# this case, registry is a WeakSet
clsdict["_abc_impl"] = [type_ for type_ in registry]

# 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
Expand Down
10 changes: 6 additions & 4 deletions cloudpickle/cloudpickle_fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,15 @@ def _class_getstate(obj):
clsdict = _extract_class_dict(obj)
clsdict.pop('__weakref__', None)

# For ABCMeta in python3.7+, remove _abc_impl as it is not picklable.
# This is a fix which breaks the cache but this only makes the first
# calls to issubclass slower.
if "_abc_impl" in clsdict:
if issubclass(type(obj), abc.ABCMeta):
# If obj is an instance of an ABCMeta subclass, dont pickle the
# cache/negative caches populated during isinstance/issubclass
# checks, but pickle the list of registered subclasses of obj.
clsdict.pop('_abc_impl', None)
(registry, _, _, _) = abc._get_dump(obj)
clsdict["_abc_impl"] = [subclass_weakref()
for subclass_weakref in registry]

if "__slots__" in clsdict:
# pickle string length optimization: member descriptors of obj are
# created automatically from obj's __slots__ attribute, no need to
Expand Down
26 changes: 26 additions & 0 deletions tests/cloudpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,32 @@ def test_getset_descriptor(self):
depickled_descriptor = pickle_depickle(float.real)
self.assertIs(depickled_descriptor, float.real)

def test_abc_cache_not_pickled(self):
# cloudpickle issue #302: make sure that cloudpickle does not pickle
# the caches populated during instance/subclass checks of abc.ABCMeta
# instances.
MyClass = abc.ABCMeta('MyClass', (), {})

class MyUnrelatedClass:
pass

class MyRelatedClass:
pass

MyClass.register(MyRelatedClass)

assert not issubclass(MyUnrelatedClass, MyClass)
assert issubclass(MyRelatedClass, MyClass)

s = cloudpickle.dumps(MyClass)

assert b"MyUnrelatedClass" not in s
assert b"MyRelatedClass" in s

depickled_class = cloudpickle.loads(s)
assert not issubclass(MyUnrelatedClass, depickled_class)
assert issubclass(MyRelatedClass, depickled_class)

def test_abc(self):

@abc.abstractmethod
Expand Down