diff -r 2959ef933b8b Lib/test/test_super.py --- a/Lib/test/test_super.py Mon Jun 04 18:59:10 2012 +0100 +++ b/Lib/test/test_super.py Mon Jun 04 21:12:15 2012 +0200 @@ -41,6 +41,26 @@ class G(A): pass +class P: + @property + def x(self): + return self._x + @x.setter + def x(self, value): + self._x = value + @x.deleter + def x(self): + del self._x + +class Q(P): + pass + +class R(P): + x = None + +class S(Q, R): + pass + class TestSuper(unittest.TestCase): @@ -115,6 +135,60 @@ return __class__ self.assertIs(X.f(), X) + def test_super_setattr(self): + # See issue14965 + val = object() + p = P() + with self.assertRaises(AttributeError): + super(P, p).x = val + with self.assertRaises(AttributeError): + del super(P, p).x + + q = Q() + super(Q, q).x = val + self.assertIs(q.__dict__['_x'], val) + self.assertIs(super(Q, q).x, val) + del super(Q, q).x + self.assertNotIn('_x', q.__dict__) + + r = R() + super(R, r).x = val + self.assertIs(r.__dict__['_x'], val) + self.assertIs(super(R, r).x, val) + del super(R, r).x + self.assertNotIn('_x', r.__dict__) + + s = S() + with self.assertRaises(AttributeError): + super(S, s).x = val + with self.assertRaises(AttributeError): + del super(S, s).x + self.assertIs(super(S, s).x, None) + + def test_super_property(self): + # See issue14965 + class T(P): + def __init__(self, x): + self.x = x + @property + def x(self): + return super().x / 2 + @x.setter + def x(self, value): + super().x = value * 2 + @x.deleter + def x(self): + del super().x + t = T(1) + self.assertEqual(t.x, 1) + self.assertEqual(t._x, 2) + t.x = 10 + self.assertEqual(t.x, 10) + self.assertEqual(t._x, 20) + del t.x + self.assertFalse(hasattr(t, 'x')) + self.assertFalse(hasattr(t, '_x')) + def test_main(): support.run_unittest(TestSuper) diff -r 2959ef933b8b Objects/typeobject.c --- a/Objects/typeobject.c Mon Jun 04 18:59:10 2012 +0100 +++ b/Objects/typeobject.c Mon Jun 04 21:12:15 2012 +0200 @@ -6301,6 +6301,71 @@ return PyObject_GenericGetAttr(self, name); } +static int +super_setattro(PyObject *self, PyObject *name, PyObject *value) +{ + superobject *su = (superobject *)self; + int skip = (su->obj_type == NULL); + + if (!skip) { + /* We don't allow overwriting __class__. */ + skip = (PyUnicode_Check(name) && + PyUnicode_GET_LENGTH(name) == 9 && + PyUnicode_CompareWithASCIIString(name, "__class__") == 0); + } + + if (!skip) { + PyObject *mro, *descr, *tmp, *dict; + PyTypeObject *starttype; + descrsetfunc f; + Py_ssize_t i, n; + int done = 0, result = 0; + + starttype = su->obj_type; + mro = starttype->tp_mro; + + if (mro == NULL) + n = 0; + else { + assert(PyTuple_Check(mro)); + n = PyTuple_GET_SIZE(mro); + } + for (i = 0; i < n; i++) { + if ((PyObject *)(su->type) == PyTuple_GET_ITEM(mro, i)) + break; + } + i++; + descr = NULL; + /* keep a strong reference to mro because starttype->tp_mro can be + replaced during PyDict_GetItem(dict, name) */ + Py_INCREF(mro); + for (; i < n; i++) { + tmp = PyTuple_GET_ITEM(mro, i); + if (PyType_Check(tmp)) + dict = ((PyTypeObject *)tmp)->tp_dict; + else + continue; + descr = PyDict_GetItem(dict, name); + if (descr != NULL) { + Py_INCREF(descr); + f = Py_TYPE(descr)->tp_descr_set; + if ((f != NULL) && PyDescr_IsData(descr)) { + /* We found a data descriptor: */ + result = f(descr, su->obj, value); + done = 1; + } + Py_DECREF(descr); + break; + } + } + Py_DECREF(mro); + if (done) { + return result; + } + } + return PyObject_GenericSetAttr(self, name, value); +} + static PyTypeObject * supercheck(PyTypeObject *type, PyObject *obj) { @@ -6528,7 +6593,7 @@ 0, /* tp_call */ 0, /* tp_str */ super_getattro, /* tp_getattro */ - 0, /* tp_setattro */ + super_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */