cPickle.c, load_build(): Taught cPickle how to pick apart

the optional proto 2 slot state.

pickle.py, load_build():  CAUTION:  Noted that cPickle's
load_build and pickle's load_build really don't do the same
things with the state, and didn't before this patch either.
cPickle never tries to do .update(), and has no backoff if
instance.__dict__ can't be retrieved.  There are no tests
that can tell the difference, and part of what cPickle's
load_build() did looked accidental to me, so I don't know
what the true intent is here.

pickletester.py, test_pickle.py:  Got rid of the hack for
exempting cPickle from running some of the proto 2 tests.

dictobject.c, PyDict_Next():  documented intended use.
This commit is contained in:
Tim Peters 2003-02-15 03:01:11 +00:00
parent d2c684f79f
commit 080c88b912
5 changed files with 90 additions and 32 deletions

View file

@ -1249,6 +1249,10 @@ class Unpickler:
# the instance variables. This is a semantic # the instance variables. This is a semantic
# difference when unpickling in restricted # difference when unpickling in restricted
# vs. unrestricted modes. # vs. unrestricted modes.
# Note, however, that cPickle has never tried to do the
# .update() business, and always uses
# PyObject_SetItem(inst.__dict__, key, value) in a
# loop over state.items().
for k, v in state.items(): for k, v in state.items():
setattr(inst, k, v) setattr(inst, k, v)
if slotstate: if slotstate:

View file

@ -728,12 +728,6 @@ class AbstractPickleTests(unittest.TestCase):
self.assertEqual(y.abc, 666) self.assertEqual(y.abc, 666)
self.assertEqual(x.__dict__, y.__dict__) self.assertEqual(x.__dict__, y.__dict__)
# XXX Temporary hack, so long as the C implementation of pickle protocol
# XXX 2 isn't ready. When it is, move the methods in TempAbstractPickleTests
# XXX into AbstractPickleTests above, and get rid of TempAbstractPickleTests
# XXX along with the references to it in test_pickle.py.
class TempAbstractPickleTests(unittest.TestCase):
def test_newobj_list_slots(self): def test_newobj_list_slots(self):
x = SlotList([1, 2, 3]) x = SlotList([1, 2, 3])
x.foo = 42 x.foo = 42
@ -745,6 +739,7 @@ class TempAbstractPickleTests(unittest.TestCase):
self.assertEqual(x.foo, y.foo) self.assertEqual(x.foo, y.foo)
self.assertEqual(x.bar, y.bar) self.assertEqual(x.bar, y.bar)
class MyInt(int): class MyInt(int):
sample = 1 sample = 1

View file

@ -5,11 +5,10 @@ from cStringIO import StringIO
from test import test_support from test import test_support
from test.pickletester import AbstractPickleTests from test.pickletester import AbstractPickleTests
from test.pickletester import TempAbstractPickleTests as XXXTemp
from test.pickletester import AbstractPickleModuleTests from test.pickletester import AbstractPickleModuleTests
from test.pickletester import AbstractPersistentPicklerTests from test.pickletester import AbstractPersistentPicklerTests
class PickleTests(AbstractPickleTests, AbstractPickleModuleTests, XXXTemp): class PickleTests(AbstractPickleTests, AbstractPickleModuleTests):
def dumps(self, arg, proto=0, fast=0): def dumps(self, arg, proto=0, fast=0):
# Ignore fast # Ignore fast

View file

@ -4309,43 +4309,93 @@ load_setitems(Unpicklerobject *self)
static int static int
load_build(Unpicklerobject *self) load_build(Unpicklerobject *self)
{ {
PyObject *value = 0, *inst = 0, *instdict = 0, *d_key = 0, *d_value = 0, PyObject *state, *inst, *slotstate;
*junk = 0, *__setstate__ = 0; PyObject *__setstate__;
int i, r = 0; PyObject *d_key, *d_value;
int i;
int res = -1;
if (self->stack->length < 2) return stackUnderflow(); /* Stack is ... instance, state. We want to leave instance at
PDATA_POP(self->stack, value); * the stack top, possibly mutated via instance.__setstate__(state).
if (! value) return -1; */
inst=self->stack->data[self->stack->length-1]; if (self->stack->length < 2)
return stackUnderflow();
PDATA_POP(self->stack, state);
if (state == NULL)
return -1;
inst = self->stack->data[self->stack->length - 1];
if ((__setstate__ = PyObject_GetAttr(inst, __setstate___str))) { __setstate__ = PyObject_GetAttr(inst, __setstate___str);
ARG_TUP(self, value); if (__setstate__ != NULL) {
PyObject *junk = NULL;
/* The explicit __setstate__ is responsible for everything. */
ARG_TUP(self, state);
if (self->arg) { if (self->arg) {
junk = PyObject_Call(__setstate__, self->arg, NULL); junk = PyObject_Call(__setstate__, self->arg, NULL);
FREE_ARG_TUP(self); FREE_ARG_TUP(self);
} }
Py_DECREF(__setstate__); Py_DECREF(__setstate__);
if (! junk) return -1; if (junk == NULL)
return -1;
Py_DECREF(junk); Py_DECREF(junk);
return 0; return 0;
} }
PyErr_Clear(); PyErr_Clear();
if ((instdict = PyObject_GetAttr(inst, __dict___str))) {
/* A default __setstate__. First see whether state embeds a
* slot state dict too (a proto 2 addition).
*/
if (PyTuple_Check(state) && PyTuple_Size(state) == 2) {
PyObject *temp = state;
state = PyTuple_GET_ITEM(temp, 0);
slotstate = PyTuple_GET_ITEM(temp, 1);
Py_INCREF(state);
Py_INCREF(slotstate);
Py_DECREF(temp);
}
else
slotstate = NULL;
/* Set inst.__dict__ from the state dict (if any). */
if (state != Py_None) {
PyObject *dict;
if (! PyDict_Check(state)) {
PyErr_SetString(UnpicklingError, "state is not a "
"dictionary");
goto finally;
}
dict = PyObject_GetAttr(inst, __dict___str);
if (dict == NULL)
goto finally;
i = 0; i = 0;
while (PyDict_Next(value, &i, &d_key, &d_value)) { while (PyDict_Next(state, &i, &d_key, &d_value)) {
if (PyObject_SetItem(instdict, d_key, d_value) < 0) { if (PyObject_SetItem(dict, d_key, d_value) < 0)
r=-1; goto finally;
break;
} }
Py_DECREF(dict);
} }
Py_DECREF(instdict);
}
else r=-1;
Py_XDECREF(value); /* Also set instance attributes from the slotstate dict (if any). */
if (slotstate != NULL) {
if (! PyDict_Check(slotstate)) {
PyErr_SetString(UnpicklingError, "slot state is not "
"a dictionary");
goto finally;
}
i = 0;
while (PyDict_Next(slotstate, &i, &d_key, &d_value)) {
if (PyObject_SetAttr(inst, d_key, d_value) < 0)
goto finally;
}
}
res = 0;
return r; finally:
Py_DECREF(state);
Py_XDECREF(slotstate);
return res;
} }

View file

@ -642,7 +642,17 @@ PyDict_Clear(PyObject *op)
PyMem_DEL(table); PyMem_DEL(table);
} }
/* CAUTION: In general, it isn't safe to use PyDict_Next in a loop that /*
* Iterate over a dict. Use like so:
*
* int i;
* PyObject *key, *value;
* i = 0; # important! i should not otherwise be changed by you
* while (PyDict_Next(yourdict, &i, &key, &value) {
* Refer to borrowed references in key and value.
* }
*
* CAUTION: In general, it isn't safe to use PyDict_Next in a loop that
* mutates the dict. One exception: it is safe if the loop merely changes * mutates the dict. One exception: it is safe if the loop merely changes
* the values associated with the keys (but doesn't insert new keys or * the values associated with the keys (but doesn't insert new keys or
* delete keys), via PyDict_SetItem(). * delete keys), via PyDict_SetItem().