Issue #7689: Allow pickling of dynamically created classes when their
metaclass is registered with copyreg. Patch by Nicolas M. ThiƩry and
Craig Citro.
diff --git a/Lib/pickle.py b/Lib/pickle.py
index 005b1b9..5b95cba 100644
--- a/Lib/pickle.py
+++ b/Lib/pickle.py
@@ -286,20 +286,20 @@
f(self, obj) # Call unbound method with explicit self
return
- # Check for a class with a custom metaclass; treat as regular class
- try:
- issc = issubclass(t, TypeType)
- except TypeError: # t is not a class (old Boost; see SF #502085)
- issc = 0
- if issc:
- self.save_global(obj)
- return
-
# Check copy_reg.dispatch_table
reduce = dispatch_table.get(t)
if reduce:
rv = reduce(obj)
else:
+ # Check for a class with a custom metaclass; treat as regular class
+ try:
+ issc = issubclass(t, TypeType)
+ except TypeError: # t is not a class (old Boost; see SF #502085)
+ issc = 0
+ if issc:
+ self.save_global(obj)
+ return
+
# Check for a __reduce_ex__ method, fall back to __reduce__
reduce = getattr(obj, "__reduce_ex__", None)
if reduce:
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py
index c9e74b8..2e3875c 100644
--- a/Lib/test/pickletester.py
+++ b/Lib/test/pickletester.py
@@ -124,6 +124,19 @@
class use_metaclass(object):
__metaclass__ = metaclass
+class pickling_metaclass(type):
+ def __eq__(self, other):
+ return (type(self) == type(other) and
+ self.reduce_args == other.reduce_args)
+
+ def __reduce__(self):
+ return (create_dynamic_class, self.reduce_args)
+
+def create_dynamic_class(name, bases):
+ result = pickling_metaclass(name, bases, dict())
+ result.reduce_args = (name, bases)
+ return result
+
# DATA0 .. DATA2 are the pickles we expect under the various protocols, for
# the object returned by create_data().
@@ -609,6 +622,14 @@
b = self.loads(s)
self.assertEqual(a.__class__, b.__class__)
+ def test_dynamic_class(self):
+ a = create_dynamic_class("my_dynamic_class", (object,))
+ copy_reg.pickle(pickling_metaclass, pickling_metaclass.__reduce__)
+ for proto in protocols:
+ s = self.dumps(a, proto)
+ b = self.loads(s)
+ self.assertEqual(a, b)
+
def test_structseq(self):
import time
import os
diff --git a/Misc/ACKS b/Misc/ACKS
index 5fd0bfa..a807956 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -147,6 +147,7 @@
Tom Christiansen
Vadim Chugunov
David Cinege
+Craig Citro
Mike Clarkson
Andrew Clegg
Brad Clements
@@ -817,6 +818,7 @@
Mikhail Terekhov
Richard M. Tew
Tobias Thelen
+Nicolas M. Thiéry
James Thomas
Robin Thomas
Stephen Thorne
diff --git a/Misc/NEWS b/Misc/NEWS
index 68d32a6..714bf01 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -50,6 +50,10 @@
Library
-------
+- Issue #7689: Allow pickling of dynamically created classes when their
+ metaclass is registered with copy_reg. Patch by Nicolas M. Thiéry and
+ Craig Citro.
+
- Issue #13058: ossaudiodev: fix a file descriptor leak on error. Patch by
Thomas Jarosch.
diff --git a/Modules/cPickle.c b/Modules/cPickle.c
index bd577ad..48b5075 100644
--- a/Modules/cPickle.c
+++ b/Modules/cPickle.c
@@ -2697,11 +2697,6 @@
}
}
- if (PyType_IsSubtype(type, &PyType_Type)) {
- res = save_global(self, args, NULL);
- goto finally;
- }
-
/* Get a reduction callable, and call it. This may come from
* copy_reg.dispatch_table, the object's __reduce_ex__ method,
* or the object's __reduce__ method.
@@ -2717,6 +2712,11 @@
}
}
else {
+ if (PyType_IsSubtype(type, &PyType_Type)) {
+ res = save_global(self, args, NULL);
+ goto finally;
+ }
+
/* Check for a __reduce_ex__ method. */
__reduce__ = PyObject_GetAttr(args, __reduce_ex___str);
if (__reduce__ != NULL) {