From 8c8425531638fcab2093639d29c413cdb3bea787 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 14 Mar 2002 23:05:54 +0000 Subject: [PATCH] "Fix" for SF bug #520644: __slots__ are not pickled. As promised in my response to the bug report, I'm not really fixing it; in fact, one could argule over what the proper fix should do. Instead, I'm adding a little magic that raises TypeError if you try to pickle an instance of a class that has __slots__ but doesn't define or override __getstate__. This is done by adding a bozo __getstate__ that always raises TypeError. Bugfix candidate (also the checkin to typeobject.c, of course). --- Lib/test/test_descr.py | 89 ++++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 6 +++ 2 files changed, 95 insertions(+) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 7e488846163..1188e1d560e 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -2450,6 +2450,94 @@ def pickles(): print "a = x =", a print "b = y =", b +def pickleslots(): + if verbose: print "Testing pickling of classes with __slots__ ..." + import pickle, cPickle + # Pickling of classes with __slots__ but without __getstate__ should fail + global B, C, D, E + class B(object): + pass + for base in [object, B]: + class C(base): + __slots__ = ['a'] + class D(C): + pass + try: + pickle.dumps(C()) + except TypeError: + pass + else: + raise TestFailed, "should fail: pickle C instance - %s" % base + try: + cPickle.dumps(C()) + except TypeError: + pass + else: + raise TestFailed, "should fail: cPickle C instance - %s" % base + try: + pickle.dumps(C()) + except TypeError: + pass + else: + raise TestFailed, "should fail: pickle D instance - %s" % base + try: + cPickle.dumps(D()) + except TypeError: + pass + else: + raise TestFailed, "should fail: cPickle D instance - %s" % base + # Give C a __getstate__ and __setstate__ + class C(base): + __slots__ = ['a'] + def __getstate__(self): + try: + d = self.__dict__.copy() + except AttributeError: + d = {} + try: + d['a'] = self.a + except AttributeError: + pass + return d + def __setstate__(self, d): + for k, v in d.items(): + setattr(self, k, v) + class D(C): + pass + # Now it should work + x = C() + y = pickle.loads(pickle.dumps(x)) + vereq(hasattr(y, 'a'), 0) + y = cPickle.loads(cPickle.dumps(x)) + vereq(hasattr(y, 'a'), 0) + x.a = 42 + y = pickle.loads(pickle.dumps(x)) + vereq(y.a, 42) + y = cPickle.loads(cPickle.dumps(x)) + vereq(y.a, 42) + x = D() + x.a = 42 + x.b = 100 + y = pickle.loads(pickle.dumps(x)) + vereq(y.a + y.b, 142) + y = cPickle.loads(cPickle.dumps(x)) + vereq(y.a + y.b, 142) + # But a subclass that adds a slot should not work + class E(C): + __slots__ = ['b'] + try: + pickle.dumps(E()) + except TypeError: + pass + else: + raise TestFailed, "should fail: pickle E instance - %s" % base + try: + cPickle.dumps(E()) + except TypeError: + pass + else: + raise TestFailed, "should fail: cPickle E instance - %s" % base + def copies(): if verbose: print "Testing copy.copy() and copy.deepcopy()..." import copy @@ -2798,6 +2886,7 @@ def test_main(): strops() deepcopyrecursive() modules() + pickleslots() if verbose: print "All OK" if __name__ == "__main__": diff --git a/Misc/NEWS b/Misc/NEWS index 3f1abd89c8d..0b8b3422342 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -6,6 +6,12 @@ Type/class unification and new-style classes Core and builtins +- If you try to pickle an instance of a class that has __slots__ but + doesn't define or override __getstate__, a TypeError is now raised. + This is done by adding a bozo __getstate__ to the class that always + raises TypeError. (Before, this would appear to be pickled, but the + state of the slots would be lost.) + - PyErr_Display will provide file and line information for all exceptions that have an attribute print_file_and_line, not just SyntaxErrors.