]> granicus.if.org Git - python/commitdiff
This is my patch:
authorMichael W. Hudson <mwh@python.net>
Tue, 26 Nov 2002 14:47:27 +0000 (14:47 +0000)
committerMichael W. Hudson <mwh@python.net>
Tue, 26 Nov 2002 14:47:27 +0000 (14:47 +0000)
[ 635933 ] make some type attrs writable

Plus a couple of extra tests beyond what's up there.

It hasn't been as carefully reviewed as it perhaps should, so all readers
are encouraged, nay exhorted, to give this a close reading.

There are still a couple of oddities related to assigning to __name__,
but I intend to solicit python-dev's opinions on these.

Lib/test/test_descr.py
Objects/typeobject.c

index 2c2a42b0c8089ce5ec8defce65946864af16954d..b230d39fe7a781218467ef46f57ec91e86dc4a5a 100644 (file)
@@ -3434,6 +3434,93 @@ def do_this_first():
     # (before PyType_Ready(tuple) is called)
     type.mro(tuple)
 
+def mutable_bases():
+    # stuff that should work:
+    class C(object):
+        pass
+    class C2(object):
+        def __getattribute__(self, attr):
+            if attr == 'a':
+                return 2
+            else:
+                return super(C2, self).__getattribute__(attr)        
+        def meth(self):
+            return 1
+    class D(C):
+        pass
+    class E(D):
+        pass
+    d = D()
+    e = E()
+    D.__bases__ = (C2,)
+    vereq(d.meth(), 1)
+    vereq(e.meth(), 1)
+    vereq(d.a, 2)
+    vereq(e.a, 2)
+    vereq(C2.__subclasses__(), [D])
+
+    # stuff that shouldn't:
+    class L(list):
+        pass
+
+    try:
+        L.__bases__ = (dict,)
+    except TypeError:
+        pass
+    else:
+        raise TestFailed, "shouldn't turn list subclass into dict subclass"
+
+    try:
+        list.__bases__ = (dict,)
+    except TypeError:
+        pass
+    else:
+        raise TestFailed, "shouldn't be able to assign to list.__bases__"
+
+    try:
+        del D.__bases__
+    except TypeError:
+        pass
+    else:
+        raise TestFailed, "shouldn't be able to delete .__bases__"
+
+    try:
+        D.__bases__ = (D,)
+    except TypeError:
+        pass
+    else:
+        # actually, we'll have crashed by here...
+        raise TestFailed, "shouldn't be able to create inheritance cycles"
+
+    # let's throw a classic class into the mix:
+    class Classic:
+        def meth2(self):
+            return 3
+
+    D.__bases__ = (C, Classic)
+
+    vereq(d.meth2(), 3)
+    vereq(e.meth2(), 3)
+    try:
+        d.a
+    except AttributeError:
+        pass
+    else:
+        raise TestFailed, "attribute should have vanished"
+
+    try:
+        D.__bases__ = (Classic,)
+    except TypeError:
+        pass
+    else:
+        raise TestFailed, "new-style class must have a new-style base"
+
+def mutable_names():
+    class C(object):
+        pass
+
+    C.__name__ = 'C'
+
 def test_main():
     do_this_first()
     class_docstrings()
@@ -3513,6 +3600,8 @@ def test_main():
     slotmultipleinheritance()
     testrmul()
     testipow()
+    mutable_bases()
+    mutable_names()
     if verbose: print "All OK"
 
 if __name__ == "__main__":
index fc93d897ae4b794cd5098b1688fd2355fff1b040..995d85f7ebf16ad72a0715eb88cfa9a5f4760e32 100644 (file)
@@ -32,7 +32,6 @@ static PyMemberDef type_members[] = {
        {"__base__", T_OBJECT, offsetof(PyTypeObject, tp_base), READONLY},
        {"__dictoffset__", T_LONG,
         offsetof(PyTypeObject, tp_dictoffset), READONLY},
-       {"__bases__", T_OBJECT, offsetof(PyTypeObject, tp_bases), READONLY},
        {"__mro__", T_OBJECT, offsetof(PyTypeObject, tp_mro), READONLY},
        {0}
 };
@@ -50,6 +49,46 @@ type_name(PyTypeObject *type, void *context)
        return PyString_FromString(s);
 }
 
+static int
+type_set_name(PyTypeObject *type, PyObject *value, void *context)
+{
+       etype* et;
+
+       if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
+               PyErr_Format(PyExc_TypeError,
+                            "can't set %s.__name__", type->tp_name);
+               return -1;
+       }
+       if (!value) {
+               PyErr_Format(PyExc_TypeError,
+                            "can't delete %s.__name__", type->tp_name);
+               return -1;
+       }
+       if (!PyString_Check(value)) {
+               PyErr_Format(PyExc_TypeError,
+                            "can only assign string to %s.__name__, not '%s'",
+                            type->tp_name, value->ob_type->tp_name);
+               return -1;
+       }
+       if (strlen(PyString_AS_STRING(value)) 
+           != (size_t)PyString_GET_SIZE(value)) {
+               PyErr_Format(PyExc_ValueError,
+                            "__name__ must not contain null bytes");
+               return -1;
+       }
+
+       et = (etype*)type;
+
+       Py_INCREF(value);
+
+       Py_DECREF(et->name);
+       et->name = value;
+
+       type->tp_name = PyString_AS_STRING(value);
+
+       return 0;
+}
+
 static PyObject *
 type_module(PyTypeObject *type, void *context)
 {
@@ -63,7 +102,7 @@ type_module(PyTypeObject *type, void *context)
        if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE))
                return PyString_FromString("__builtin__");
        mod = PyDict_GetItemString(type->tp_dict, "__module__");
-       if (mod != NULL && PyString_Check(mod)) {
+       if (mod != NULL) {
                Py_INCREF(mod);
                return mod;
        }
@@ -74,8 +113,7 @@ type_module(PyTypeObject *type, void *context)
 static int
 type_set_module(PyTypeObject *type, PyObject *value, void *context)
 {
-       if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE) ||
-           strrchr(type->tp_name, '.')) {
+       if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
                PyErr_Format(PyExc_TypeError,
                             "can't set %s.__module__", type->tp_name);
                return -1;
@@ -85,9 +123,164 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
                             "can't delete %s.__module__", type->tp_name);
                return -1;
        }
+
        return PyDict_SetItemString(type->tp_dict, "__module__", value);
 }
 
+static PyObject *
+type_get_bases(PyTypeObject *type, void *context)
+{
+       Py_INCREF(type->tp_bases);
+       return type->tp_bases;
+}
+
+static PyTypeObject *best_base(PyObject *);
+static int mro_internal(PyTypeObject *);
+static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *);
+static int add_subclass(PyTypeObject*, PyTypeObject*);
+static void remove_subclass(PyTypeObject *, PyTypeObject *);
+static void update_all_slots(PyTypeObject *);
+
+static int
+mro_subclasses(PyTypeObject *type)
+{
+       PyTypeObject *subclass;
+       PyObject *ref, *subclasses, *old_mro;
+       int i, n, r;
+
+       subclasses = type->tp_subclasses;
+       if (subclasses == NULL)
+               return 0;
+       assert(PyList_Check(subclasses));
+       n = PyList_GET_SIZE(subclasses);
+       for (i = 0; i < n; i++) {
+               ref = PyList_GET_ITEM(subclasses, i);
+               assert(PyWeakref_CheckRef(ref));
+               subclass = (PyTypeObject *)PyWeakref_GET_OBJECT(ref);
+               assert(subclass != NULL);
+               if ((PyObject *)subclass == Py_None)
+                       continue;
+               assert(PyType_Check(subclass));
+               old_mro = subclass->tp_mro;
+               if (mro_internal(subclass) < 0) {
+                       subclass->tp_mro = old_mro;
+                       r = -1;
+               }
+               else {
+                       Py_DECREF(old_mro);
+               }
+               if (mro_subclasses(subclass) < 0)
+                       r = -1;
+       }
+       return r;
+}
+
+static int
+type_set_bases(PyTypeObject *type, PyObject *value, void *context)
+{
+       int i, r = 0;
+       PyObject* ob;
+       PyTypeObject *new_base, *old_base;
+       PyObject *old_bases, *old_mro;
+
+       if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
+               PyErr_Format(PyExc_TypeError,
+                            "can't set %s.__bases__", type->tp_name);
+               return -1;
+       }
+       if (!value) {
+               PyErr_Format(PyExc_TypeError,
+                            "can't delete %s.__bases__", type->tp_name);
+               return -1;
+       }
+       if (!PyTuple_Check(value)) {
+               PyErr_Format(PyExc_TypeError,
+                    "can only assign tuple to %s.__bases__, not %s",
+                            type->tp_name, value->ob_type->tp_name);
+               return -1;
+       }
+       for (i = 0; i < PyTuple_GET_SIZE(value); i++) {
+               ob = PyTuple_GET_ITEM(value, i);
+               if (!PyClass_Check(ob) && !PyType_Check(ob)) {
+                       PyErr_Format(
+                               PyExc_TypeError,
+       "%s.__bases__ must be tuple of old- or new-style classes, not '%s'",
+                               type->tp_name, ob->ob_type->tp_name);
+                       return -1;
+               }
+               if (PyType_IsSubtype(type, (PyTypeObject*)ob)) {
+                       PyErr_SetString(PyExc_TypeError,
+               "a __bases__ item causes an inheritance cycle");
+                       return -1;
+               }
+       }
+
+       new_base = best_base(value);
+
+       if (!new_base) {
+               return -1;
+       }
+
+       if (!compatible_for_assignment(type->tp_base, new_base, "__bases__"))
+               return -1;
+
+       Py_INCREF(new_base);
+       Py_INCREF(value);
+
+       old_bases = type->tp_bases;
+       old_base = type->tp_base;
+       old_mro = type->tp_mro;
+
+       type->tp_bases = value;
+       type->tp_base = new_base;
+
+       if (mro_internal(type) < 0) {
+               type->tp_bases = old_bases;
+               type->tp_base = old_base;
+               type->tp_mro = old_mro;
+
+               Py_DECREF(value);
+               Py_DECREF(new_base);
+
+               return -1;
+       }
+
+       if (mro_subclasses(type) < 0)
+               r = -1;
+
+       /* any base that was in __bases__ but now isn't, we
+          need to remove |type| from it's tp_subclasses.
+          conversely, any class now in __bases__ that wasn't
+          needs to have |type| added to it's subclasses. */
+
+       /* for now, sod that: just remove from all old_bases,
+          add to all new_bases */
+
+       for (i = PyTuple_GET_SIZE(old_bases) - 1; i >= 0; i--) {
+               ob = PyTuple_GET_ITEM(old_bases, i);
+               if (PyType_Check(ob)) {
+                       remove_subclass(
+                               (PyTypeObject*)ob, type);
+               }
+       }
+
+       for (i = PyTuple_GET_SIZE(value) - 1; i >= 0; i--) {
+               ob = PyTuple_GET_ITEM(value, i);
+               if (PyType_Check(ob)) {
+                       if (add_subclass((PyTypeObject*)ob, type) < 0)
+                               r = -1;
+               }
+       }
+
+       update_all_slots(type);
+
+       Py_DECREF(old_bases);
+       Py_DECREF(old_base);
+       Py_DECREF(old_mro);
+
+       return r;
+}
+
 static PyObject *
 type_dict(PyTypeObject *type, void *context)
 {
@@ -120,7 +313,8 @@ type_get_doc(PyTypeObject *type, void *context)
 }
 
 static PyGetSetDef type_getsets[] = {
-       {"__name__", (getter)type_name, NULL, NULL},
+       {"__name__", (getter)type_name, (setter)type_set_name, NULL},
+       {"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL},
        {"__module__", (getter)type_module, (setter)type_set_module, NULL},
        {"__dict__",  (getter)type_dict,  NULL, NULL},
        {"__doc__", (getter)type_get_doc, NULL, NULL},
@@ -2025,11 +2219,48 @@ same_slots_added(PyTypeObject *a, PyTypeObject *b)
        return size == a->tp_basicsize && size == b->tp_basicsize;
 }
 
+static int
+compatible_for_assignment(PyTypeObject* old, PyTypeObject* new, char* attr)
+{
+       PyTypeObject *newbase, *oldbase;
+
+       if (new->tp_dealloc != old->tp_dealloc ||
+           new->tp_free != old->tp_free)
+       {
+               PyErr_Format(PyExc_TypeError,
+                            "%s assignment: "
+                            "'%s' deallocator differs from '%s'",
+                            attr,
+                            new->tp_name,
+                            old->tp_name);
+               return 0;
+       }
+       newbase = new;
+       oldbase = old;
+       while (equiv_structs(newbase, newbase->tp_base))
+               newbase = newbase->tp_base;
+       while (equiv_structs(oldbase, oldbase->tp_base))
+               oldbase = oldbase->tp_base;
+       if (newbase != oldbase &&
+           (newbase->tp_base != oldbase->tp_base ||
+            !same_slots_added(newbase, oldbase))) {
+               PyErr_Format(PyExc_TypeError,
+                            "%s assignment: "
+                            "'%s' object layout differs from '%s'",
+                            attr,
+                            new->tp_name,
+                            old->tp_name);
+               return 0;
+       }
+       
+       return 1;
+}
+
 static int
 object_set_class(PyObject *self, PyObject *value, void *closure)
 {
        PyTypeObject *old = self->ob_type;
-       PyTypeObject *new, *newbase, *oldbase;
+       PyTypeObject *new;
 
        if (value == NULL) {
                PyErr_SetString(PyExc_TypeError,
@@ -2050,36 +2281,15 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
                             "__class__ assignment: only for heap types");
                return -1;
        }
-       if (new->tp_dealloc != old->tp_dealloc ||
-           new->tp_free != old->tp_free)
-       {
-               PyErr_Format(PyExc_TypeError,
-                            "__class__ assignment: "
-                            "'%s' deallocator differs from '%s'",
-                            new->tp_name,
-                            old->tp_name);
-               return -1;
+       if (compatible_for_assignment(new, old, "__class__")) {
+               Py_INCREF(new);
+               self->ob_type = new;
+               Py_DECREF(old);
+               return 0;
        }
-       newbase = new;
-       oldbase = old;
-       while (equiv_structs(newbase, newbase->tp_base))
-               newbase = newbase->tp_base;
-       while (equiv_structs(oldbase, oldbase->tp_base))
-               oldbase = oldbase->tp_base;
-       if (newbase != oldbase &&
-           (newbase->tp_base != oldbase->tp_base ||
-            !same_slots_added(newbase, oldbase))) {
-               PyErr_Format(PyExc_TypeError,
-                            "__class__ assignment: "
-                            "'%s' object layout differs from '%s'",
-                            new->tp_name,
-                            old->tp_name);
+       else {
                return -1;
        }
-       Py_INCREF(new);
-       self->ob_type = new;
-       Py_DECREF(old);
-       return 0;
 }
 
 static PyGetSetDef object_getsets[] = {
@@ -2478,7 +2688,6 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
 }
 
 static int add_operators(PyTypeObject *);
-static int add_subclass(PyTypeObject *base, PyTypeObject *type);
 
 int
 PyType_Ready(PyTypeObject *type)
@@ -2641,6 +2850,28 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
        return i;
 }
 
+static void
+remove_subclass(PyTypeObject *base, PyTypeObject *type)
+{
+       int i;
+       PyObject *list, *ref;
+
+       list = base->tp_subclasses;
+       if (list == NULL) {
+               return;
+       }
+       assert(PyList_Check(list));
+       i = PyList_GET_SIZE(list);
+       while (--i >= 0) {
+               ref = PyList_GET_ITEM(list, i);
+               assert(PyWeakref_CheckRef(ref));
+               if (PyWeakref_GET_OBJECT(ref) == (PyObject*)type) {
+                       /* this can't fail, right? */
+                       PySequence_DelItem(list, i);
+                       return;
+               }
+       }
+}
 
 /* Generic wrappers for overloadable 'operators' such as __getitem__ */
 
@@ -4556,6 +4787,18 @@ fixup_slot_dispatchers(PyTypeObject *type)
                p = update_one_slot(type, p);
 }
 
+static void
+update_all_slots(PyTypeObject* type)
+{
+       slotdef *p;
+
+       init_slotdefs();
+       for (p = slotdefs; p->name; p++) {
+               /* update_slot returns int but can't actually fail */
+               update_slot(type, p->name_strobj);
+       }
+}
+
 /* This function is called by PyType_Ready() to populate the type's
    dictionary with method descriptors for function slots.  For each
    function slot (like tp_repr) that's defined in the type, one or more