]> granicus.if.org Git - python/commitdiff
Another step in the right direction: when a new class's attribute
authorGuido van Rossum <guido@python.org>
Thu, 11 Oct 2001 18:33:53 +0000 (18:33 +0000)
committerGuido van Rossum <guido@python.org>
Thu, 11 Oct 2001 18:33:53 +0000 (18:33 +0000)
corresponding to a dispatch slot (e.g. __getitem__ or __add__) is set,
calculate the proper dispatch slot and propagate the change to all
subclasses.  Because of multiple inheritance, there's no easy way to
avoid always recursing down the tree of subclasses.  Who cares?

(There's more to do, but this works.  There's also a test for this now.)

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

index 50e7d9db90213de3d44fc481b39d95357f90abdd..1768a6d3d0a5453b608d70a427253a32d004f107 100644 (file)
@@ -7,6 +7,10 @@ def vereq(a, b):
     if not (a == b):
         raise TestFailed, "%r == %r" % (a, b)
 
+def veris(a, b):
+    if a is not b:
+        raise TestFailed, "%r is %r" % (a, b)
+
 def testunop(a, res, expr="len(a)", meth="__len__"):
     if verbose: print "checking", expr
     dict = {'a': a}
@@ -623,10 +627,8 @@ def metaclass():
     class autosuper(type):
         # Automatically add __super to the class
         # This trick only works for dynamic classes
-        # so we force __dynamic__ = 1
         def __new__(metaclass, name, bases, dict):
-            # XXX Should check that name isn't already a base class name
-            dict["__dynamic__"] = 1
+            assert dict.get("__dynamic__", 1)
             cls = super(autosuper, metaclass).__new__(metaclass,
                                                       name, bases, dict)
             # Name mangling for __super removes leading underscores
@@ -949,7 +951,7 @@ def dynamics():
 
     # Test handling of int*seq and seq*int
     class I(int):
-        __dynamic__ = 1
+        __dynamic__ = 1 # XXX why?
     vereq("a"*I(2), "aa")
     vereq(I(2)*"a", "aa")
     vereq(2*I(3), 6)
@@ -958,7 +960,7 @@ def dynamics():
 
     # Test handling of long*seq and seq*long
     class L(long):
-        __dynamic__ = 1
+        __dynamic__ = 1 # XXX why?
     vereq("a"*L(2L), "aa")
     vereq(L(2L)*"a", "aa")
     vereq(2*L(3), 6)
@@ -967,7 +969,7 @@ def dynamics():
 
     # Test comparison of classes with dynamic metaclasses
     class dynamicmetaclass(type):
-        __dynamic__ = 1
+        __dynamic__ = 1 # XXX ???
     class someclass:
         __metaclass__ = dynamicmetaclass
     verify(someclass != object)
@@ -1253,7 +1255,7 @@ def specials():
     verify(10 not in c1)
     # Test the default behavior for dynamic classes
     class D(object):
-        __dynamic__ = 1
+        __dynamic__ = 1 # XXX why?
         def __getitem__(self, i):
             if 0 <= i < 10: return i
             raise IndexError
@@ -1563,30 +1565,30 @@ def inherits():
     verify((+a).__class__ is float)
 
     class madcomplex(complex):
-        __dynamic__ = 0
+        __dynamic__ = 0 # XXX Shouldn't be necessary
         def __repr__(self):
             return "%.17gj%+.17g" % (self.imag, self.real)
     a = madcomplex(-3, 4)
     vereq(repr(a), "4j-3")
     base = complex(-3, 4)
-    verify(base.__class__ is complex)
+    veris(base.__class__, complex)
     vereq(a, base)
     vereq(complex(a), base)
-    verify(complex(a).__class__ is complex)
+    veris(complex(a).__class__, complex)
     a = madcomplex(a)  # just trying another form of the constructor
     vereq(repr(a), "4j-3")
     vereq(a, base)
     vereq(complex(a), base)
-    verify(complex(a).__class__ is complex)
+    veris(complex(a).__class__, complex)
     vereq(hash(a), hash(base))
-    verify((+a).__class__ is complex)
-    verify((a + 0).__class__ is complex)
+    veris((+a).__class__, complex)
+    veris((a + 0).__class__, complex)
     vereq(a + 0, base)
-    verify((a - 0).__class__ is complex)
+    veris((a - 0).__class__, complex)
     vereq(a - 0, base)
-    verify((a * 1).__class__ is complex)
+    veris((a * 1).__class__, complex)
     vereq(a * 1, base)
-    verify((a / 1).__class__ is complex)
+    veris((a / 1).__class__, complex)
     vereq(a / 1, base)
 
     class madtuple(tuple):
@@ -2237,6 +2239,66 @@ def binopoverride():
         def __eq__(self, other):
             return self.lower() == other.lower()
 
+def subclasspropagation():
+    if verbose: print "Testing propagation of slot functions to subclasses..."
+    class A(object):
+        pass
+    class B(A):
+        pass
+    class C(A):
+        pass
+    class D(B, C):
+        pass
+    d = D()
+    vereq(hash(d), id(d))
+    A.__hash__ = lambda self: 42
+    vereq(hash(d), 42)
+    C.__hash__ = lambda self: 314
+    vereq(hash(d), 314)
+    B.__hash__ = lambda self: 144
+    vereq(hash(d), 144)
+    D.__hash__ = lambda self: 100
+    vereq(hash(d), 100)
+    del D.__hash__
+    vereq(hash(d), 144)
+    del B.__hash__
+    vereq(hash(d), 314)
+    del C.__hash__
+    vereq(hash(d), 42)
+    del A.__hash__
+    vereq(hash(d), id(d))
+    d.foo = 42
+    d.bar = 42
+    vereq(d.foo, 42)
+    vereq(d.bar, 42)
+    def __getattribute__(self, name):
+        if name == "foo":
+            return 24
+        return object.__getattribute__(self, name)
+    A.__getattribute__ = __getattribute__
+    vereq(d.foo, 24)
+    vereq(d.bar, 42)
+    def __getattr__(self, name):
+        if name in ("spam", "foo", "bar"):
+            return "hello"
+        raise AttributeError, name
+    B.__getattr__ = __getattr__
+    vereq(d.spam, "hello")
+    vereq(d.foo, 24)
+    vereq(d.bar, 42)
+    del A.__getattribute__
+    vereq(d.foo, 42)
+    del d.foo
+    vereq(d.foo, "hello")
+    vereq(d.bar, 42)
+    del B.__getattr__
+    try:
+        d.foo
+    except AttributeError:
+        pass
+    else:
+        raise TestFailed, "d.foo should be undefined now"
+    
 
 def test_main():
     class_docstrings()
@@ -2284,6 +2346,7 @@ def test_main():
     pickles()
     copies()
     binopoverride()
+    subclasspropagation()
     if verbose: print "All OK"
 
 if __name__ == "__main__":
index 1b519711ded83d2f57960501e7709bb1c7dc707c..ced80ec21f68ae292d0d99632f4b125711c0a9b4 100644 (file)
@@ -676,7 +676,7 @@ solid_base(PyTypeObject *type)
 
 staticforward void object_dealloc(PyObject *);
 staticforward int object_init(PyObject *, PyObject *, PyObject *);
-staticforward int update_slot(PyTypeObject *, PyObject *, PyObject *);
+staticforward int update_slot(PyTypeObject *, PyObject *);
 staticforward void fixup_slot_dispatchers(PyTypeObject *);
 
 static PyObject *
@@ -1107,7 +1107,7 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
        if (type->tp_flags & Py_TPFLAGS_DYNAMICTYPE) {
                if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0)
                        return -1;
-               return update_slot(type, name, value);
+               return update_slot(type, name);
        }
        PyErr_SetString(PyExc_TypeError, "can't set static type attributes");
        return -1;
@@ -3679,6 +3679,7 @@ typedef struct {
        int offset;
        void *function;
        wrapperfunc wrapper;
+       PyObject *name_strobj;
 } slotdef;
 
 #undef TPSLOT
@@ -3797,9 +3798,7 @@ static slotdef slotdefs[] = {
               slot_nb_inplace_true_divide, wrap_binaryfunc),
 
        TPSLOT("__str__", tp_str, slot_tp_str, wrap_unaryfunc),
-       TPSLOT("__str__", tp_print, NULL, NULL),
        TPSLOT("__repr__", tp_repr, slot_tp_repr, wrap_unaryfunc),
-       TPSLOT("__repr__", tp_print, NULL, NULL),
        TPSLOT("__cmp__", tp_compare, _PyObject_SlotCompare, wrap_cmpfunc),
        TPSLOT("__hash__", tp_hash, slot_tp_hash, wrap_hashfunc),
        TPSLOT("__call__", tp_call, slot_tp_call, wrap_call),
@@ -3827,35 +3826,6 @@ static slotdef slotdefs[] = {
        {NULL}
 };
 
-static int
-update_slot(PyTypeObject *type, PyObject *name, PyObject *value)
-{
-       char *s;
-       int n;
-       slotdef *p;
-       void **ptr;
-
-       assert(type->tp_flags & Py_TPFLAGS_HEAPTYPE);
-       if (value == NULL)
-               return 0; /* Can't unset a slot */
-       s = PyString_AsString(name);
-       n = PyString_Size(name);
-       if (s == NULL || n < 0) {
-               /* Shouldn't happen, but can't be bothered */
-               PyErr_Clear();
-               return 0;
-       }
-       if (!(s[0] == '_' && s[1] == '_' && s[n-1] == '_' && s[n-2] == '_'))
-               return 0;
-       for (p = slotdefs; p->name; p++) {
-               if (!strcmp(p->name, s)) {
-                       ptr = (void **) ((char *)type + p->offset);
-                       *ptr = p->function;
-               }
-       }
-       return 0;
-}
-
 static void **
 slotptr(PyTypeObject *type, int offset)
 {
@@ -3883,6 +3853,119 @@ slotptr(PyTypeObject *type, int offset)
        return (void **)ptr;
 }
 
+staticforward int recurse_down_subclasses(PyTypeObject *type, int offset);
+
+static int
+update_one_slot(PyTypeObject *type, int offset)
+{
+       slotdef *p;
+       PyObject *descr;
+       PyWrapperDescrObject *d;
+       void *generic = NULL, *specific = NULL;
+       int use_generic = 0;
+       void **ptr;
+
+       for (p = slotdefs; p->name; p++) {
+               if (p->offset != offset)
+                       continue;
+               descr = _PyType_Lookup(type, p->name_strobj);
+               if (descr == NULL)
+                       continue;
+               ptr = slotptr(type, p->offset);
+               if (ptr == NULL)
+                       continue;
+               generic = p->function;
+               if (descr->ob_type == &PyWrapperDescr_Type) {
+                       d = (PyWrapperDescrObject *)descr;
+                       if (d->d_base->wrapper == p->wrapper &&
+                           PyType_IsSubtype(type, d->d_type)) {
+                               if (specific == NULL ||
+                                   specific == d->d_wrapped)
+                                       specific = d->d_wrapped;
+                               else
+                                       use_generic = 1;
+                       }
+               }
+               else
+                       use_generic = 1;
+               if (specific && !use_generic)
+                       *ptr = specific;
+               else
+                       *ptr = generic;
+       }
+       if (recurse_down_subclasses(type, offset) < 0)
+               return -1;
+       return 0;
+}
+
+static int
+recurse_down_subclasses(PyTypeObject *type, int offset)
+{
+       PyTypeObject *subclass;
+       PyObject *ref, *subclasses;
+       int i, n;
+
+       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);
+               if (subclass == NULL)
+                       continue;
+               assert(PyType_Check(subclass));
+               if (update_one_slot(subclass, offset) < 0)
+                       return -1;
+       }
+       return 0;
+}
+
+static void
+init_name_strobj(void)
+{
+       slotdef *p;
+       static int initialized = 0;
+
+       if (initialized)
+               return;
+       for (p = slotdefs; p->name; p++) {
+               p->name_strobj = PyString_InternFromString(p->name);
+               if (!p->name_strobj)
+                       Py_FatalError("XXX ouch");
+       }
+       initialized = 1;
+}
+
+static void
+collect_offsets(PyObject *name, int offsets[])
+{
+       slotdef *p;
+
+       init_name_strobj();
+       for (p = slotdefs; p->name; p++) {
+               if (name == p->name_strobj)
+                       *offsets++ = p->offset;
+       }
+       *offsets = 0;
+}
+
+static int
+update_slot(PyTypeObject *type, PyObject *name)
+{
+       int offsets[10];
+       int *ip;
+
+       collect_offsets(name, offsets);
+       for (ip = offsets; *ip; ip++) {
+               if (update_one_slot(type, *ip) < 0)
+                       return -1;
+       }
+       return 0;
+}
+
 static void
 fixup_slot_dispatchers(PyTypeObject *type)
 {