From ffd41d9f101e31973b8713e884c95118fceb6f59 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Tue, 4 Oct 2011 09:23:04 +0200 Subject: =?UTF-8?q?Issue=20#7689:=20Allow=20pickling=20of=20dynamically=20?= =?UTF-8?q?created=20classes=20when=20their=20metaclass=20is=20registered?= =?UTF-8?q?=20with=20copyreg.=20=20Patch=20by=20Nicolas=20M.=20Thi=C3=A9ry?= =?UTF-8?q?=20and=20Craig=20Citro.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Lib/pickle.py | 18 +++++++++--------- Lib/test/pickletester.py | 21 +++++++++++++++++++++ Misc/ACKS | 2 ++ Misc/NEWS | 4 ++++ Modules/_pickle.c | 8 ++++---- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Lib/pickle.py b/Lib/pickle.py index 32ae02b..c770ff8 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -299,20 +299,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, 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 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, 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__ reduce = getattr(obj, "__reduce_ex__", None) if reduce: diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index c2ed0d2..a22acc0 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -121,6 +121,19 @@ class metaclass(type): class use_metaclass(object, metaclass=metaclass): 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 # the object returned by create_data(). @@ -695,6 +708,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,)) + 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): import time import os diff --git a/Misc/ACKS b/Misc/ACKS index 5bf395b..15fb385 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -164,6 +164,7 @@ Anders Chrigström Tom Christiansen Vadim Chugunov David Cinege +Craig Citro Mike Clarkson Andrew Clegg Brad Clements @@ -881,6 +882,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 aebd899..2961776 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -36,6 +36,10 @@ Core and Builtins 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 #13034: When decoding some SSL certificates, the subjectAltName diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 164d864..cb58349 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -3141,10 +3141,6 @@ save(PicklerObject *self, PyObject *obj, int pers_save) status = save_global(self, obj, NULL); goto done; } - else if (PyType_IsSubtype(type, &PyType_Type)) { - status = save_global(self, obj, NULL); - goto done; - } /* XXX: This part needs some unit tests. */ @@ -3163,6 +3159,10 @@ save(PicklerObject *self, PyObject *obj, int pers_save) Py_INCREF(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 { static PyObject *reduce_str = NULL; static PyObject *reduce_ex_str = NULL; -- cgit v0.12