summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael W. Hudson <mwh@python.net>2002-11-26 14:47:27 (GMT)
committerMichael W. Hudson <mwh@python.net>2002-11-26 14:47:27 (GMT)
commit98bbc49c54c4bc7de33f1f23fc364dbb8e3fe034 (patch)
tree0bb7306788ba55e7c8aa6a813395d4107b58f339
parent50905d0ffbdf7415e1ab74f7b12f7e2c112a25e0 (diff)
downloadcpython-98bbc49c54c4bc7de33f1f23fc364dbb8e3fe034.zip
cpython-98bbc49c54c4bc7de33f1f23fc364dbb8e3fe034.tar.gz
cpython-98bbc49c54c4bc7de33f1f23fc364dbb8e3fe034.tar.bz2
This is my patch:
[ 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.
-rw-r--r--Lib/test/test_descr.py89
-rw-r--r--Objects/typeobject.c311
2 files changed, 366 insertions, 34 deletions
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 2c2a42b..b230d39 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -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__":
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index fc93d89..995d85f 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -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,10 +123,165 @@ 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)
{
if (type->tp_dict == NULL) {
@@ -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},
@@ -2026,10 +2220,47 @@ same_slots_added(PyTypeObject *a, PyTypeObject *b)
}
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