cPickle produces NEWOBJ appropriately now. It still doesn't know

how to unpickle the new slot-full state tuples.
This commit is contained in:
Tim Peters 2003-02-14 23:05:28 +00:00
parent 9cac1c4574
commit 71fcda5ba7
2 changed files with 204 additions and 78 deletions

View File

@ -718,12 +718,6 @@ class AbstractPickleTests(unittest.TestCase):
else:
self.failUnless(num_setitems >= 2)
# 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_simple_newobj(self):
x = object.__new__(SimpleNewObj) # avoid __init__
x.abc = 666
@ -734,6 +728,12 @@ class TempAbstractPickleTests(unittest.TestCase):
self.assertEqual(y.abc, 666)
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):
x = SlotList([1, 2, 3])
x.foo = 42

View File

@ -119,6 +119,12 @@ static PyObject *extension_cache;
/* For looking up name pairs in copy_reg._extension_registry. */
static PyObject *two_tuple;
/* object.__reduce__, the default reduce callable. */
PyObject *object_reduce;
/* copy_reg._better_reduce, the protocol 2 reduction function. */
PyObject *better_reduce;
static PyObject *__class___str, *__getinitargs___str, *__dict___str,
*__getstate___str, *__setstate___str, *__name___str, *__reduce___str,
*write_str, *append_str,
@ -2181,38 +2187,142 @@ save_pers(Picklerobject *self, PyObject *args, PyObject *f)
return res;
}
/* We're saving ob, and args is the 2-thru-5 tuple returned by the
* appropriate __reduce__ method for ob.
*/
static int
save_reduce(Picklerobject *self, PyObject *callable,
PyObject *tup, PyObject *state, PyObject *ob)
save_reduce(Picklerobject *self, PyObject *args, PyObject *ob)
{
static char reduce = REDUCE, build = BUILD;
PyObject *callable;
PyObject *argtup;
PyObject *state = NULL;
PyObject *listitems = NULL;
PyObject *dictitems = NULL;
if (save(self, callable, 0) < 0)
int use_newobj = self->proto >= 2;
static char reduce = REDUCE;
static char build = BUILD;
static char newobj = NEWOBJ;
if (! PyArg_UnpackTuple(args, "save_reduce", 2, 5,
&callable,
&argtup,
&state,
&listitems,
&dictitems))
return -1;
if (save(self, tup, 0) < 0)
return -1;
if (state == Py_None)
state = NULL;
if (listitems == Py_None)
listitems = NULL;
if (dictitems == Py_None)
dictitems = NULL;
if (self->write_func(self, &reduce, 1) < 0)
return -1;
/* Protocol 2 special case: if callable's name is __newobj__, use
* NEWOBJ. This consumes a lot of code.
*/
if (use_newobj) {
PyObject *temp = PyObject_GetAttr(callable, __name___str);
if (temp == NULL) {
PyErr_Clear();
use_newobj = 0;
}
else {
use_newobj = PyString_Check(temp) &&
strcmp(PyString_AS_STRING(temp),
"__newobj__") == 0;
Py_DECREF(temp);
}
}
if (use_newobj) {
PyObject *cls;
PyObject *newargtup;
int n, i;
/* Sanity checks. */
n = PyTuple_Size(argtup);
if (n < 1) {
PyErr_SetString(PicklingError, "__newobj__ arglist "
"is empty");
return -1;
}
cls = PyTuple_GET_ITEM(argtup, 0);
if (! PyObject_HasAttrString(cls, "__new__")) {
PyErr_SetString(PicklingError, "args[0] from "
"__newobj__ args has no __new__");
return -1;
}
/* XXX How could ob be NULL? */
if (ob != NULL) {
PyObject *ob_dot_class;
ob_dot_class = PyObject_GetAttr(ob, __class___str);
if (ob_dot_class == NULL)
PyErr_Clear();
i = ob_dot_class != cls; /* true iff a problem */
Py_XDECREF(ob_dot_class);
if (i) {
PyErr_SetString(PicklingError, "args[0] from "
"__newobj__ args has the wrong class");
return -1;
}
}
/* Save the class and its __new__ arguments. */
if (save(self, cls, 0) < 0)
return -1;
newargtup = PyTuple_New(n-1); /* argtup[1:] */
if (newargtup == NULL)
return -1;
for (i = 1; i < n; ++i) {
PyObject *temp = PyTuple_GET_ITEM(argtup, i);
Py_INCREF(temp);
PyTuple_SET_ITEM(newargtup, i-1, temp);
}
i = save(self, newargtup, 0) < 0;
Py_DECREF(newargtup);
if (i < 0)
return -1;
/* Add NEWOBJ opcode. */
if (self->write_func(self, &newobj, 1) < 0)
return -1;
}
else {
/* Not using NEWOBJ. */
if (save(self, callable, 0) < 0 ||
save(self, argtup, 0) < 0 ||
self->write_func(self, &reduce, 1) < 0)
return -1;
}
/* Memoize. */
/* XXX How can ob be NULL? */
if (ob != NULL) {
if (state && !PyDict_Check(state)) {
if (put2(self, ob) < 0)
return -1;
}
else {
if (put(self, ob) < 0)
else if (put(self, ob) < 0)
return -1;
}
}
if (state) {
if (save(self, state, 0) < 0)
return -1;
if (self->write_func(self, &build, 1) < 0)
if (listitems && batch_list(self, listitems) < 0)
return -1;
if (dictitems && batch_dict(self, dictitems) < 0)
return -1;
if (state) {
if (save(self, state, 0) < 0 ||
self->write_func(self, &build, 1) < 0)
return -1;
}
@ -2223,9 +2333,10 @@ static int
save(Picklerobject *self, PyObject *args, int pers_save)
{
PyTypeObject *type;
PyObject *py_ob_id = 0, *__reduce__ = 0, *t = 0, *arg_tup = 0,
*callable = 0, *state = 0;
int res = -1, tmp, size;
PyObject *py_ob_id = 0, *__reduce__ = 0, *t = 0;
PyObject *arg_tup;
int res = -1;
int tmp, size;
if (self->nesting++ > Py_GetRecursionLimit()){
PyErr_SetString(PyExc_RuntimeError,
@ -2392,72 +2503,80 @@ save(Picklerobject *self, PyObject *args, int pers_save)
goto finally;
}
assert(t == NULL); /* just a reminder */
/* Get a reduction callable. This may come from
* copy_reg.dispatch_table, the object's __reduce__ method,
* the default object.__reduce__, or copy_reg._better_reduce.
*/
__reduce__ = PyDict_GetItem(dispatch_table, (PyObject *)type);
if (__reduce__ != NULL) {
Py_INCREF(__reduce__);
Py_INCREF(args);
ARG_TUP(self, args);
if (self->arg) {
t = PyObject_Call(__reduce__, self->arg, NULL);
FREE_ARG_TUP(self);
}
if (! t) goto finally;
}
else {
__reduce__ = PyObject_GetAttr(args, __reduce___str);
if (__reduce__ == NULL)
/* Check for a __reduce__ method.
* Subtle: get the unbound method from the class, so that
* protocol 2 can override the default __reduce__ that all
* classes inherit from object.
* XXX object.__reduce__ should really be rewritten so that
* XXX we don't need to call back into Python code here
* XXX (better_reduce), but no time to do that.
*/
__reduce__ = PyObject_GetAttr((PyObject *)type,
__reduce___str);
if (__reduce__ == NULL) {
PyErr_Clear();
else {
t = PyObject_Call(__reduce__, empty_tuple, NULL);
if (!t)
goto finally;
PyErr_SetObject(UnpickleableError, args);
goto finally;
}
if (self->proto >= 2 && __reduce__ == object_reduce) {
/* Proto 2 can do better than the default. */
Py_DECREF(__reduce__);
Py_INCREF(better_reduce);
__reduce__ = better_reduce;
}
}
if (t) {
if (PyString_Check(t)) {
res = save_global(self, args, t);
goto finally;
}
/* Call the reduction callable, setting t to the result. */
assert(__reduce__ != NULL);
assert(t == NULL);
Py_INCREF(args);
ARG_TUP(self, args);
if (self->arg) {
t = PyObject_Call(__reduce__, self->arg, NULL);
FREE_ARG_TUP(self);
}
if (t == NULL)
goto finally;
if (!PyTuple_Check(t)) {
cPickle_ErrFormat(PicklingError, "Value returned by "
"%s must be a tuple",
"O", __reduce__);
goto finally;
}
size = PyTuple_Size(t);
if (size != 3 && size != 2) {
cPickle_ErrFormat(PicklingError, "tuple returned by "
"%s must contain only two or three elements",
"O", __reduce__);
goto finally;
}
callable = PyTuple_GET_ITEM(t, 0);
arg_tup = PyTuple_GET_ITEM(t, 1);
if (size > 2) {
state = PyTuple_GET_ITEM(t, 2);
if (state == Py_None)
state = NULL;
}
if (!( PyTuple_Check(arg_tup) || arg_tup==Py_None )) {
cPickle_ErrFormat(PicklingError, "Second element of "
"tuple returned by %s must be a tuple",
"O", __reduce__);
goto finally;
}
res = save_reduce(self, callable, arg_tup, state, args);
if (PyString_Check(t)) {
res = save_global(self, args, t);
goto finally;
}
PyErr_SetObject(UnpickleableError, args);
if (! PyTuple_Check(t)) {
cPickle_ErrFormat(PicklingError, "Value returned by "
"%s must be string or tuple",
"O", __reduce__);
goto finally;
}
size = PyTuple_Size(t);
if (size < 2 || size > 5) {
cPickle_ErrFormat(PicklingError, "tuple returned by "
"%s must contain 2 through 5 elements",
"O", __reduce__);
goto finally;
}
arg_tup = PyTuple_GET_ITEM(t, 1);
if (!(PyTuple_Check(arg_tup) || arg_tup == Py_None)) {
cPickle_ErrFormat(PicklingError, "Second element of "
"tuple returned by %s must be a tuple",
"O", __reduce__);
goto finally;
}
res = save_reduce(self, t, args);
finally:
self->nesting--;
@ -5447,8 +5566,15 @@ init_stuff(PyObject *module_dict)
"_extension_cache");
if (!extension_cache) return -1;
better_reduce = PyObject_GetAttrString(copy_reg, "_better_reduce");
if (!better_reduce) return -1;
Py_DECREF(copy_reg);
object_reduce = PyObject_GetAttrString((PyObject *)&PyBaseObject_Type,
"__reduce__");
if (object_reduce == NULL) return -1;
if (!(empty_tuple = PyTuple_New(0)))
return -1;