From: Antoine Pitrou Date: Tue, 4 Oct 2011 07:34:48 +0000 (+0200) Subject: Issue #7689: Allow pickling of dynamically created classes when their X-Git-Tag: v2.7.3rc1~430 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=561a821e93dc5c5aff9c5e26a3cbe482ac154a51;p=python 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 005b1b93bc..5b95cbaca7 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -286,20 +286,20 @@ class Pickler: 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 c9e74b857f..2e3875c57b 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -124,6 +124,19 @@ class metaclass(type): 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 @@ class AbstractPickleTests(unittest.TestCase): 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 5fd0bfa5da..a807956fb2 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -147,6 +147,7 @@ Anders Chrigström Tom Christiansen Vadim Chugunov David Cinege +Craig Citro Mike Clarkson Andrew Clegg Brad Clements @@ -817,6 +818,7 @@ Anatoly Techtonik 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 68d32a67c3..714bf014a1 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -50,6 +50,10 @@ Core and Builtins 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 bd577ad3da..48b5075ec2 100644 --- a/Modules/cPickle.c +++ b/Modules/cPickle.c @@ -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 * copy_reg.dispatch_table, the object's __reduce_ex__ method, * or the object's __reduce__ method. @@ -2717,6 +2712,11 @@ save(Picklerobject *self, PyObject *args, int pers_save) } } 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) {