"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).
This commit is contained in:
Guido van Rossum 2002-03-14 23:05:54 +00:00
parent 0628dcfe1f
commit 8c84255316
2 changed files with 95 additions and 0 deletions

View File

@ -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__":

View File

@ -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.