From 3926a63d0579bbeea6ab855a31dc38b9fa56b5e3 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 25 Sep 2001 16:25:58 +0000 Subject: - 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. --- Lib/copy_reg.py | 29 ++++++++++++++++++++++ Lib/test/test_descr.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++- Lib/test/test_descrtut.py | 1 + Objects/typeobject.c | 61 ++++++++++++++++++++++++++++++++++++--------- 4 files changed, 142 insertions(+), 12 deletions(-) diff --git a/Lib/copy_reg.py b/Lib/copy_reg.py index e4f0b3c..e93c2a3 100644 --- a/Lib/copy_reg.py +++ b/Lib/copy_reg.py @@ -33,3 +33,32 @@ def pickle_complex(c): return complex, (c.real, c.imag) 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__ diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 766c399..0357a06 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -785,7 +785,7 @@ def objects(): class Cdict(object): pass x = Cdict() - verify(x.__dict__ is None) + verify(x.__dict__ == {}) x.foo = 1 verify(x.foo == 1) verify(x.__dict__ == {'foo': 1}) @@ -2032,6 +2032,66 @@ def setclass(): cant(object(), list) 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(): lists() @@ -2075,6 +2135,7 @@ def test_main(): coercions() descrdoc() setclass() + pickles() if verbose: print "All OK" if __name__ == "__main__": diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py index a0de4cc..80e7f05 100644 --- a/Lib/test/test_descrtut.py +++ b/Lib/test/test_descrtut.py @@ -206,6 +206,7 @@ Instead, you can get the same information from the list type: '__mul__', '__ne__', '__new__', + '__reduce__', '__repr__', '__rmul__', '__setattr__', diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 964164f..1354b81 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -53,6 +53,23 @@ type_module(PyTypeObject *type, void *context) 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 * type_dict(PyTypeObject *type, void *context) { @@ -93,7 +110,7 @@ type_dynamic(PyTypeObject *type, void *context) PyGetSetDef type_getsets[] = { {"__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}, {"__defined__", (getter)type_defined, NULL, NULL}, {"__dynamic__", (getter)type_dynamic, NULL, NULL}, @@ -656,14 +673,10 @@ subtype_dict(PyObject *obj, void *context) return NULL; } dict = *dictptr; - if (dict == NULL) { - Py_INCREF(Py_None); - return Py_None; - } - else { - Py_INCREF(dict); - return dict; - } + if (dict == NULL) + *dictptr = dict = PyDict_New(); + Py_XINCREF(dict); + return dict; } PyGetSetDef subtype_getsets[] = { @@ -1283,6 +1296,31 @@ static PyGetSetDef object_getsets[] = { {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 = { PyObject_HEAD_INIT(&PyType_Type) 0, /* ob_size */ @@ -1312,7 +1350,7 @@ PyTypeObject PyBaseObject_Type = { 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + object_methods, /* tp_methods */ 0, /* tp_members */ object_getsets, /* tp_getset */ 0, /* tp_base */ @@ -3009,7 +3047,8 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name) res = PyObject_GenericGetAttr(self, name); else 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(); res = PyObject_CallFunction(getattr, "OO", self, name); } -- cgit v0.12