- Provisional support for pickling new-style objects. (*)
- Made cls.__module__ writable. - Ensure that obj.__dict__ is returned as {}, not None, even upon first reference; it simply springs into life when you ask for it. (*) The pickling support is provisional for the following reasons: - It doesn't support classes with __slots__. - It relies on additional support in copy_reg.py: the C method __reduce__, defined in the object class, really calls calling copy_reg._reduce(obj). Eventually the Python code in copy_reg.py needs to be migrated to C, but I'd like to experiment with the Python implementation first. The _reduce() code also relies on an additional helper function, _reconstructor(), defined in copy_reg.py; this should also be reimplemented in C.
This commit is contained in:
parent
ad39aba2f6
commit
3926a63d05
|
@ -33,3 +33,32 @@ def pickle_complex(c):
|
||||||
return complex, (c.real, c.imag)
|
return complex, (c.real, c.imag)
|
||||||
|
|
||||||
pickle(type(1j), pickle_complex, complex)
|
pickle(type(1j), pickle_complex, complex)
|
||||||
|
|
||||||
|
# Support for picking new-style objects
|
||||||
|
|
||||||
|
_dummy_classes = {}
|
||||||
|
|
||||||
|
def _reconstructor(cls, base, state):
|
||||||
|
dummy = _dummy_classes.get(base)
|
||||||
|
if dummy is None:
|
||||||
|
class dummy(base): pass
|
||||||
|
_dummy_classes[base] = dummy
|
||||||
|
obj = dummy(state)
|
||||||
|
obj._foo = 1; del obj._foo # hack to create __dict__
|
||||||
|
obj.__class__ = cls
|
||||||
|
return obj
|
||||||
|
_reconstructor.__safe_for_unpickling__ = 1
|
||||||
|
|
||||||
|
_HEAPTYPE = 1<<9
|
||||||
|
|
||||||
|
def _reduce(self):
|
||||||
|
for base in self.__class__.__mro__:
|
||||||
|
if not base.__flags__ & _HEAPTYPE:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
base = object # not really reachable
|
||||||
|
if base is object:
|
||||||
|
state = None
|
||||||
|
else:
|
||||||
|
state = base(self)
|
||||||
|
return _reconstructor, (self.__class__, base, state), self.__dict__
|
||||||
|
|
|
@ -785,7 +785,7 @@ def objects():
|
||||||
class Cdict(object):
|
class Cdict(object):
|
||||||
pass
|
pass
|
||||||
x = Cdict()
|
x = Cdict()
|
||||||
verify(x.__dict__ is None)
|
verify(x.__dict__ == {})
|
||||||
x.foo = 1
|
x.foo = 1
|
||||||
verify(x.foo == 1)
|
verify(x.foo == 1)
|
||||||
verify(x.__dict__ == {'foo': 1})
|
verify(x.__dict__ == {'foo': 1})
|
||||||
|
@ -2032,6 +2032,66 @@ def setclass():
|
||||||
cant(object(), list)
|
cant(object(), list)
|
||||||
cant(list(), object)
|
cant(list(), object)
|
||||||
|
|
||||||
|
def pickles():
|
||||||
|
if verbose: print "Testing pickling new-style classes and objects..."
|
||||||
|
import pickle, cPickle
|
||||||
|
|
||||||
|
def sorteditems(d):
|
||||||
|
L = d.items()
|
||||||
|
L.sort()
|
||||||
|
return L
|
||||||
|
|
||||||
|
global C
|
||||||
|
class C(object):
|
||||||
|
def __init__(self, a, b):
|
||||||
|
super(C, self).__init__()
|
||||||
|
self.a = a
|
||||||
|
self.b = b
|
||||||
|
def __repr__(self):
|
||||||
|
return "C(%r, %r)" % (self.a, self.b)
|
||||||
|
|
||||||
|
global C1
|
||||||
|
class C1(list):
|
||||||
|
def __new__(cls, a, b):
|
||||||
|
return super(C1, cls).__new__(cls)
|
||||||
|
def __init__(self, a, b):
|
||||||
|
self.a = a
|
||||||
|
self.b = b
|
||||||
|
def __repr__(self):
|
||||||
|
return "C1(%r, %r)<%r>" % (self.a, self.b, list(self))
|
||||||
|
|
||||||
|
global C2
|
||||||
|
class C2(int):
|
||||||
|
def __new__(cls, a, b, val=0):
|
||||||
|
return super(C2, cls).__new__(cls, val)
|
||||||
|
def __init__(self, a, b, val=0):
|
||||||
|
self.a = a
|
||||||
|
self.b = b
|
||||||
|
def __repr__(self):
|
||||||
|
return "C2(%r, %r)<%r>" % (self.a, self.b, int(self))
|
||||||
|
|
||||||
|
for p in pickle, cPickle:
|
||||||
|
for bin in 0, 1:
|
||||||
|
|
||||||
|
for cls in C, C1, C2:
|
||||||
|
s = p.dumps(cls, bin)
|
||||||
|
cls2 = p.loads(s)
|
||||||
|
verify(cls2 is cls)
|
||||||
|
|
||||||
|
a = C1(1, 2); a.append(42); a.append(24)
|
||||||
|
b = C2("hello", "world", 42)
|
||||||
|
s = p.dumps((a, b), bin)
|
||||||
|
x, y = p.loads(s)
|
||||||
|
assert x.__class__ == a.__class__
|
||||||
|
assert sorteditems(x.__dict__) == sorteditems(a.__dict__)
|
||||||
|
assert y.__class__ == b.__class__
|
||||||
|
assert sorteditems(y.__dict__) == sorteditems(b.__dict__)
|
||||||
|
assert `x` == `a`
|
||||||
|
assert `y` == `b`
|
||||||
|
if verbose:
|
||||||
|
print "a = x =", a
|
||||||
|
print "b = y =", b
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
lists()
|
lists()
|
||||||
|
@ -2075,6 +2135,7 @@ def test_main():
|
||||||
coercions()
|
coercions()
|
||||||
descrdoc()
|
descrdoc()
|
||||||
setclass()
|
setclass()
|
||||||
|
pickles()
|
||||||
if verbose: print "All OK"
|
if verbose: print "All OK"
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -206,6 +206,7 @@ Instead, you can get the same information from the list type:
|
||||||
'__mul__',
|
'__mul__',
|
||||||
'__ne__',
|
'__ne__',
|
||||||
'__new__',
|
'__new__',
|
||||||
|
'__reduce__',
|
||||||
'__repr__',
|
'__repr__',
|
||||||
'__rmul__',
|
'__rmul__',
|
||||||
'__setattr__',
|
'__setattr__',
|
||||||
|
|
|
@ -53,6 +53,23 @@ type_module(PyTypeObject *type, void *context)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
type_set_module(PyTypeObject *type, PyObject *value, void *context)
|
||||||
|
{
|
||||||
|
if (!(type->tp_flags & Py_TPFLAGS_DYNAMICTYPE) ||
|
||||||
|
strrchr(type->tp_name, '.')) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"can't set %s.__module__", type->tp_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!value) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"can't delete %s.__module__", type->tp_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return PyDict_SetItemString(type->tp_dict, "__module__", value);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
type_dict(PyTypeObject *type, void *context)
|
type_dict(PyTypeObject *type, void *context)
|
||||||
{
|
{
|
||||||
|
@ -93,7 +110,7 @@ type_dynamic(PyTypeObject *type, void *context)
|
||||||
|
|
||||||
PyGetSetDef type_getsets[] = {
|
PyGetSetDef type_getsets[] = {
|
||||||
{"__name__", (getter)type_name, NULL, NULL},
|
{"__name__", (getter)type_name, NULL, NULL},
|
||||||
{"__module__", (getter)type_module, NULL, NULL},
|
{"__module__", (getter)type_module, (setter)type_set_module, NULL},
|
||||||
{"__dict__", (getter)type_dict, NULL, NULL},
|
{"__dict__", (getter)type_dict, NULL, NULL},
|
||||||
{"__defined__", (getter)type_defined, NULL, NULL},
|
{"__defined__", (getter)type_defined, NULL, NULL},
|
||||||
{"__dynamic__", (getter)type_dynamic, NULL, NULL},
|
{"__dynamic__", (getter)type_dynamic, NULL, NULL},
|
||||||
|
@ -656,14 +673,10 @@ subtype_dict(PyObject *obj, void *context)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
dict = *dictptr;
|
dict = *dictptr;
|
||||||
if (dict == NULL) {
|
if (dict == NULL)
|
||||||
Py_INCREF(Py_None);
|
*dictptr = dict = PyDict_New();
|
||||||
return Py_None;
|
Py_XINCREF(dict);
|
||||||
}
|
return dict;
|
||||||
else {
|
|
||||||
Py_INCREF(dict);
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyGetSetDef subtype_getsets[] = {
|
PyGetSetDef subtype_getsets[] = {
|
||||||
|
@ -1283,6 +1296,31 @@ static PyGetSetDef object_getsets[] = {
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
object_reduce(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
/* Call copy_reg._reduce(self) */
|
||||||
|
static PyObject *copy_reg_str;
|
||||||
|
PyObject *copy_reg, *res;
|
||||||
|
|
||||||
|
if (!copy_reg_str) {
|
||||||
|
copy_reg_str = PyString_InternFromString("copy_reg");
|
||||||
|
if (copy_reg_str == NULL)
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
copy_reg = PyImport_Import(copy_reg_str);
|
||||||
|
if (!copy_reg)
|
||||||
|
return NULL;
|
||||||
|
res = PyEval_CallMethod(copy_reg, "_reduce", "(O)", self);
|
||||||
|
Py_DECREF(copy_reg);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef object_methods[] = {
|
||||||
|
{"__reduce__", object_reduce, METH_NOARGS, "helper for pickle"},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
PyTypeObject PyBaseObject_Type = {
|
PyTypeObject PyBaseObject_Type = {
|
||||||
PyObject_HEAD_INIT(&PyType_Type)
|
PyObject_HEAD_INIT(&PyType_Type)
|
||||||
0, /* ob_size */
|
0, /* ob_size */
|
||||||
|
@ -1312,7 +1350,7 @@ PyTypeObject PyBaseObject_Type = {
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
0, /* tp_iter */
|
0, /* tp_iter */
|
||||||
0, /* tp_iternext */
|
0, /* tp_iternext */
|
||||||
0, /* tp_methods */
|
object_methods, /* tp_methods */
|
||||||
0, /* tp_members */
|
0, /* tp_members */
|
||||||
object_getsets, /* tp_getset */
|
object_getsets, /* tp_getset */
|
||||||
0, /* tp_base */
|
0, /* tp_base */
|
||||||
|
@ -3009,7 +3047,8 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name)
|
||||||
res = PyObject_GenericGetAttr(self, name);
|
res = PyObject_GenericGetAttr(self, name);
|
||||||
else
|
else
|
||||||
res = PyObject_CallFunction(getattribute, "OO", self, name);
|
res = PyObject_CallFunction(getattribute, "OO", self, name);
|
||||||
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
if (getattr != NULL &&
|
||||||
|
res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
res = PyObject_CallFunction(getattr, "OO", self, name);
|
res = PyObject_CallFunction(getattr, "OO", self, name);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue