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

metaclass is registered with copyreg.  Patch by Nicolas M. Thiéry and
Craig Citro.
This commit is contained in:
Antoine Pitrou 2011-10-04 09:34:48 +02:00
parent ad349a190e
commit 561a821e93
5 changed files with 41 additions and 14 deletions

View file

@ -286,20 +286,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, 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 # Check copy_reg.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, 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__ # 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:

View file

@ -124,6 +124,19 @@ class metaclass(type):
class use_metaclass(object): class use_metaclass(object):
__metaclass__ = metaclass __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 # DATA0 .. DATA2 are the pickles we expect under the various protocols, for
# the object returned by create_data(). # the object returned by create_data().
@ -609,6 +622,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,))
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): def test_structseq(self):
import time import time
import os import os

View file

@ -147,6 +147,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
@ -817,6 +818,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

View file

@ -50,6 +50,10 @@ Core and Builtins
Library 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 - Issue #13058: ossaudiodev: fix a file descriptor leak on error. Patch by
Thomas Jarosch. Thomas Jarosch.

View file

@ -2697,11 +2697,6 @@ save(Picklerobject *self, PyObject *args, int pers_save)
} }
} }
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 /* Get a reduction callable, and call it. This may come from
* copy_reg.dispatch_table, the object's __reduce_ex__ method, * copy_reg.dispatch_table, the object's __reduce_ex__ method,
* or the object's __reduce__ method. * or the object's __reduce__ method.
@ -2717,6 +2712,11 @@ save(Picklerobject *self, PyObject *args, int pers_save)
} }
} }
else { else {
if (PyType_IsSubtype(type, &PyType_Type)) {
res = save_global(self, args, NULL);
goto finally;
}
/* Check for a __reduce_ex__ method. */ /* Check for a __reduce_ex__ method. */
__reduce__ = PyObject_GetAttr(args, __reduce_ex___str); __reduce__ = PyObject_GetAttr(args, __reduce_ex___str);
if (__reduce__ != NULL) { if (__reduce__ != NULL) {