Commit ffd41d9f authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #7689: Allow pickling of dynamically created classes when their

metaclass is registered with copyreg.  Patch by Nicolas M. Thiéry and
Craig Citro.
parent 1efb33a6
...@@ -299,20 +299,20 @@ class _Pickler: ...@@ -299,20 +299,20 @@ class _Pickler:
f(self, obj) # Call unbound method with explicit self f(self, obj) # Call unbound method with explicit self
return return
# Check for a class with a custom metaclass; treat as regular class
try:
issc = issubclass(t, type)
except TypeError: # t is not a class (old Boost; see SF #502085)
issc = 0
if issc:
self.save_global(obj)
return
# Check copyreg.dispatch_table # Check copyreg.dispatch_table
reduce = dispatch_table.get(t) reduce = dispatch_table.get(t)
if reduce: if reduce:
rv = reduce(obj) rv = reduce(obj)
else: else:
# Check for a class with a custom metaclass; treat as regular class
try:
issc = issubclass(t, type)
except TypeError: # t is not a class (old Boost; see SF #502085)
issc = False
if issc:
self.save_global(obj)
return
# Check for a __reduce_ex__ method, fall back to __reduce__ # Check for a __reduce_ex__ method, fall back to __reduce__
reduce = getattr(obj, "__reduce_ex__", None) reduce = getattr(obj, "__reduce_ex__", None)
if reduce: if reduce:
......
...@@ -121,6 +121,19 @@ class metaclass(type): ...@@ -121,6 +121,19 @@ class metaclass(type):
class use_metaclass(object, metaclass=metaclass): class use_metaclass(object, metaclass=metaclass):
pass pass
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 # DATA0 .. DATA2 are the pickles we expect under the various protocols, for
# the object returned by create_data(). # the object returned by create_data().
...@@ -695,6 +708,14 @@ class AbstractPickleTests(unittest.TestCase): ...@@ -695,6 +708,14 @@ class AbstractPickleTests(unittest.TestCase):
b = self.loads(s) b = self.loads(s)
self.assertEqual(a.__class__, b.__class__) self.assertEqual(a.__class__, b.__class__)
def test_dynamic_class(self):
a = create_dynamic_class("my_dynamic_class", (object,))
copyreg.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): def test_structseq(self):
import time import time
import os import os
......
...@@ -164,6 +164,7 @@ Anders Chrigström ...@@ -164,6 +164,7 @@ Anders Chrigström
Tom Christiansen Tom Christiansen
Vadim Chugunov Vadim Chugunov
David Cinege David Cinege
Craig Citro
Mike Clarkson Mike Clarkson
Andrew Clegg Andrew Clegg
Brad Clements Brad Clements
...@@ -881,6 +882,7 @@ Anatoly Techtonik ...@@ -881,6 +882,7 @@ Anatoly Techtonik
Mikhail Terekhov Mikhail Terekhov
Richard M. Tew Richard M. Tew
Tobias Thelen Tobias Thelen
Nicolas M. Thiéry
James Thomas James Thomas
Robin Thomas Robin Thomas
Stephen Thorne Stephen Thorne
......
...@@ -36,6 +36,10 @@ Core and Builtins ...@@ -36,6 +36,10 @@ Core and Builtins
Library Library
------- -------
- Issue #7689: Allow pickling of dynamically created classes when their
metaclass is registered with copyreg. Patch by Nicolas M. Thiéry and Craig
Citro.
- Issue #4147: minidom's toprettyxml no longer adds whitespace to text nodes. - Issue #4147: minidom's toprettyxml no longer adds whitespace to text nodes.
- Issue #13034: When decoding some SSL certificates, the subjectAltName - Issue #13034: When decoding some SSL certificates, the subjectAltName
......
...@@ -3141,10 +3141,6 @@ save(PicklerObject *self, PyObject *obj, int pers_save) ...@@ -3141,10 +3141,6 @@ save(PicklerObject *self, PyObject *obj, int pers_save)
status = save_global(self, obj, NULL); status = save_global(self, obj, NULL);
goto done; goto done;
} }
else if (PyType_IsSubtype(type, &PyType_Type)) {
status = save_global(self, obj, NULL);
goto done;
}
/* XXX: This part needs some unit tests. */ /* XXX: This part needs some unit tests. */
...@@ -3163,6 +3159,10 @@ save(PicklerObject *self, PyObject *obj, int pers_save) ...@@ -3163,6 +3159,10 @@ save(PicklerObject *self, PyObject *obj, int pers_save)
Py_INCREF(obj); Py_INCREF(obj);
reduce_value = _Pickler_FastCall(self, reduce_func, obj); reduce_value = _Pickler_FastCall(self, reduce_func, obj);
} }
else if (PyType_IsSubtype(type, &PyType_Type)) {
status = save_global(self, obj, NULL);
goto done;
}
else { else {
static PyObject *reduce_str = NULL; static PyObject *reduce_str = NULL;
static PyObject *reduce_ex_str = NULL; static PyObject *reduce_ex_str = NULL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment