[Zope3-checkins] SVN: Zope3/trunk/src/zope/ Added C implementations of interface __call__ and __adapt__. Also

Jim Fulton jim at zope.com
Sun Apr 30 09:56:44 EDT 2006


Log message for revision 67761:
  Added C implementations of interface __call__ and __adapt__.  Also
  Added __call__ for declarations as an alternatite to isOrExtends.
  This allows invocation through a slot, which is slightly faster.
  
  These changes won back most of the losses in request time I have up
  when I fixed an adapter-cache bug yesterday. :)
  
  This will also improve debugging a little bit, since it will no longer
  be necessary to step through the rather uninteresting __call__ and
  __adapt__ methods.
  

Changed:
  U   Zope3/trunk/src/zope/interface/README.txt
  U   Zope3/trunk/src/zope/interface/_zope_interface_coptimizations.c
  U   Zope3/trunk/src/zope/interface/interface.py
  U   Zope3/trunk/src/zope/interface/tests/test_interface.py
  U   Zope3/trunk/src/zope/security/checker.py

-=-
Modified: Zope3/trunk/src/zope/interface/README.txt
===================================================================
--- Zope3/trunk/src/zope/interface/README.txt	2006-04-30 13:45:31 UTC (rev 67760)
+++ Zope3/trunk/src/zope/interface/README.txt	2006-04-30 13:56:44 UTC (rev 67761)
@@ -670,9 +670,117 @@
 
   >>> del errors[:]
 
+==========
+Adaptation
+==========
 
+Interfaces can be called to perform adaptation.
 
+The sematics based on those of the PEP 246 adapt function.
 
+If an object cannot be adapted, then a TypeError is raised::
+
+  >>> class I(zope.interface.Interface):
+  ...     pass
+
+  >>> I(0)
+  Traceback (most recent call last):
+  ...
+  TypeError: ('Could not adapt', 0, <InterfaceClass __main__.I>)
+
+
+
+unless an alternate value is provided as a second positional argument::
+
+  >>> I(0, 'bob')
+  'bob'
+
+If an object already implements the interface, then it will be returned::
+
+  >>> class C(object):
+  ...     zope.interface.implements(I)
+
+  >>> obj = C()
+  >>> I(obj) is obj
+  True
+
+If an object implements __conform__, then it will be used::
+
+  >>> class C(object):
+  ...     zope.interface.implements(I)
+  ...     def __conform__(self, proto):
+  ...          return 0
+
+  >>> I(C())
+  0
+
+Adapter hooks (see __adapt__) will also be used, if present:
+
+  >>> from zope.interface.interface import adapter_hooks
+  >>> def adapt_0_to_42(iface, obj):
+  ...     if obj == 0:
+  ...         return 42
+
+  >>> adapter_hooks.append(adapt_0_to_42)
+  >>> I(0)
+  42
+
+  >>> adapter_hooks.remove(adapt_0_to_42)
+  >>> I(0)
+  Traceback (most recent call last):
+  ...
+  TypeError: ('Could not adapt', 0, <InterfaceClass __main__.I>)
+
+__adapt__
+=========
+
+  >>> class I(zope.interface.Interface):
+  ...     pass
+
+Interfaces implement the PEP 246 __adapt__ method.
+
+This method is normally not called directly. It is called by the PEP
+246 adapt framework and by the interface __call__ operator.
+
+The adapt method is responsible for adapting an object to the
+reciever.
+
+The default version returns None::
+
+  >>> I.__adapt__(0)
+
+unless the object given provides the interface::
+
+  >>> class C(object):
+  ...     zope.interface.implements(I)
+
+  >>> obj = C()
+  >>> I.__adapt__(obj) is obj
+  True
+
+Adapter hooks can be provided (or removed) to provide custom
+adaptation. We'll install a silly hook that adapts 0 to 42.
+We install a hook by simply adding it to the adapter_hooks
+list::
+
+  >>> from zope.interface.interface import adapter_hooks
+  >>> def adapt_0_to_42(iface, obj):
+  ...     if obj == 0:
+  ...         return 42
+
+  >>> adapter_hooks.append(adapt_0_to_42)
+  >>> I.__adapt__(0)
+  42
+
+Hooks must either return an adapter, or None if no adapter can
+be found.
+
+Hooks can be uninstalled by removing them from the list::
+
+  >>> adapter_hooks.remove(adapt_0_to_42)
+  >>> I.__adapt__(0)
+
+
 .. [#create] The main reason we subclass `Interface` is to cause the
              Python class statement to create an interface, rather
              than a class.

Modified: Zope3/trunk/src/zope/interface/_zope_interface_coptimizations.c
===================================================================
--- Zope3/trunk/src/zope/interface/_zope_interface_coptimizations.c	2006-04-30 13:45:31 UTC (rev 67760)
+++ Zope3/trunk/src/zope/interface/_zope_interface_coptimizations.c	2006-04-30 13:56:44 UTC (rev 67761)
@@ -21,8 +21,9 @@
 
 static PyObject *str__dict__, *str__implemented__, *strextends;
 static PyObject *BuiltinImplementationSpecifications, *str__provides__;
-static PyObject *str__class__, *str__providedBy__, *strisOrExtends;
+static PyObject *str__class__, *str__providedBy__;
 static PyObject *empty, *fallback, *str_implied, *str_cls, *str_implements;
+static PyObject *str__conform__, *str_call_conform, *adapter_hooks;
 static PyTypeObject *Implements;
 
 static int imported_declarations = 0;
@@ -233,12 +234,22 @@
   return result;
 }
 
+/* 
+   Get an attribute from an inst dict. Return a borrowed reference.
+  
+   This has a number of advantages:
+
+   - It avoids layers of Python api
+
+   - It doesn't waste time looking for descriptors
+
+   - It fails wo raising an exception, although that shouldn't really
+     matter.
+
+*/
 static PyObject *
 inst_attr(PyObject *self, PyObject *name)
 {
-  /* Get an attribute from an inst dict. Return a borrowed reference.
-   */
-
   PyObject **dictp, *v;
 
   dictp = _PyObject_GetDictPtr(self);
@@ -280,6 +291,16 @@
 ;
 
 static PyObject *
+Spec_call(PyObject *self, PyObject *args, PyObject *kw)
+{
+  PyObject *spec;
+
+  if (! PyArg_ParseTuple(args, "O", &spec))
+    return NULL;
+  return Spec_extends(self, spec);
+}
+
+static PyObject *
 Spec_providedBy(PyObject *self, PyObject *ob)
 {
   PyObject *decl, *item;
@@ -288,13 +309,13 @@
   if (decl == NULL)
     return NULL;
 
-  if (PyObject_TypeCheck(ob, &SpecType))
+  if (PyObject_TypeCheck(decl, &SpecType))
     item = Spec_extends(decl, self);
   else
     /* decl is probably a security proxy.  We have to go the long way
        around. 
     */
-    item = PyObject_CallMethodObjArgs(decl, strisOrExtends, self, NULL);
+    item = PyObject_CallFunctionObjArgs(decl, self, NULL);
 
   Py_DECREF(decl);
   return item;
@@ -318,7 +339,7 @@
   if (PyObject_TypeCheck(decl, &SpecType))
     item = Spec_extends(decl, self);
   else
-    item = PyObject_CallMethodObjArgs(decl, strisOrExtends, self, NULL);
+    item = PyObject_CallFunctionObjArgs(decl, self, NULL);
 
   Py_DECREF(decl);
   return item;
@@ -354,7 +375,7 @@
 	/* tp_as_sequence    */ 0,
 	/* tp_as_mapping     */ 0,
 	/* tp_hash           */ (hashfunc)0,
-	/* tp_call           */ (ternaryfunc)0,
+	/* tp_call           */ (ternaryfunc)Spec_call,
 	/* tp_str            */ (reprfunc)0,
         /* tp_getattro       */ (getattrofunc)0,
         /* tp_setattro       */ (setattrofunc)0,
@@ -488,7 +509,195 @@
         /* tp_descr_get      */ (descrgetfunc)CPB_descr_get,
 };
 
+/* ==================================================================== */
+/* ========== Begin: __call__ and __adapt__ descriptors =============== */
 
+/*
+    def __adapt__(self, obj):
+        """Adapt an object to the reciever
+        """
+        if self.providedBy(obj):
+            return obj
+
+        for hook in adapter_hooks:
+            adapter = hook(self, obj)
+            if adapter is not None:
+                return adapter
+
+  
+*/
+static PyObject *
+__adapt__(PyObject *self, PyObject *obj)
+{
+  PyObject *decl, *args, *adapter;
+  int implements, i, l;
+
+  decl = providedBy(NULL, obj);
+  if (decl == NULL)
+    return NULL;
+
+  if (PyObject_TypeCheck(decl, &SpecType))
+    {
+      PyObject *implied;
+
+      implied = inst_attr(decl, str_implied);
+      if (implied == NULL)
+        {
+          Py_DECREF(decl);
+          return NULL;
+        }
+
+      implements = PyDict_GetItem(implied, self) != NULL;
+      Py_DECREF(decl);
+    }
+  else
+    {
+      /* decl is probably a security proxy.  We have to go the long way
+         around. 
+      */
+      PyObject *r;
+      r = PyObject_CallFunctionObjArgs(decl, self, NULL);
+      Py_DECREF(decl);
+      if (r == NULL)
+        return NULL;
+      implements = PyObject_IsTrue(r);
+      Py_DECREF(r);
+    }
+
+  if (implements)
+    {
+      Py_INCREF(obj);
+      return obj;
+    }
+
+  l = PyList_GET_SIZE(adapter_hooks);
+  args = PyTuple_New(2);
+  if (args == NULL)
+    return NULL;
+  Py_INCREF(self);
+  PyTuple_SET_ITEM(args, 0, self);
+  Py_INCREF(obj);
+  PyTuple_SET_ITEM(args, 1, obj);
+  for (i = 0; i < l; i++)
+    {
+      adapter = PyObject_CallObject(PyList_GET_ITEM(adapter_hooks, i), args);
+      if (adapter == NULL || adapter != Py_None)
+        {
+          Py_DECREF(args);
+          return adapter;
+        }
+      Py_DECREF(adapter);
+    }
+
+  Py_DECREF(args);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+static struct PyMethodDef ib_methods[] = {
+  {"__adapt__",	(PyCFunction)__adapt__, METH_O,
+   "Adapt an object to the reciever"},
+  {NULL,		NULL}		/* sentinel */
+};
+
+/* 
+        def __call__(self, obj, alternate=_marker):
+            conform = getattr(obj, '__conform__', None)
+            if conform is not None:
+                adapter = self._call_conform(conform)
+                if adapter is not None:
+                    return adapter
+
+            adapter = self.__adapt__(obj)
+
+            if adapter is not None:
+                return adapter
+            elif alternate is not _marker:
+                return alternate
+            else:
+                raise TypeError("Could not adapt", obj, self)
+*/
+static PyObject *
+ib_call(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+  PyObject *conform, *obj, *alternate=NULL, *adapter;
+  
+  static char *kwlist[] = {"obj", "alternate", NULL};
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist,
+                                   &obj, &alternate))
+    return NULL;
+
+  conform = PyObject_GetAttr(obj, str__conform__);
+  if (conform != NULL)
+    {
+      adapter = PyObject_CallMethodObjArgs(self, str_call_conform,
+                                           conform, NULL);
+      Py_DECREF(conform);
+      if (adapter == NULL || adapter != Py_None)
+        return adapter;
+      Py_DECREF(adapter);
+    }
+  else
+    PyErr_Clear();
+ 
+  adapter = __adapt__(self, obj);
+  if (adapter == NULL || adapter != Py_None)
+    return adapter;
+  Py_DECREF(adapter);
+
+  if (alternate != NULL)
+    {
+      Py_INCREF(alternate);
+      return alternate;
+    }
+
+  adapter = Py_BuildValue("sOO", "Could not adapt", obj, self);
+  if (adapter != NULL)
+    {
+      PyErr_SetObject(PyExc_TypeError, adapter);
+      Py_DECREF(adapter);
+    }
+  return NULL;
+}
+
+static PyTypeObject InterfaceBase = {
+	PyObject_HEAD_INIT(NULL)
+	/* ob_size           */ 0,
+	/* tp_name           */ "_zope_interface_coptimizations."
+                                "InterfaceBase",
+	/* tp_basicsize      */ 0,
+	/* tp_itemsize       */ 0,
+	/* tp_dealloc        */ (destructor)0,
+	/* tp_print          */ (printfunc)0,
+	/* tp_getattr        */ (getattrfunc)0,
+	/* tp_setattr        */ (setattrfunc)0,
+	/* tp_compare        */ (cmpfunc)0,
+	/* tp_repr           */ (reprfunc)0,
+	/* tp_as_number      */ 0,
+	/* tp_as_sequence    */ 0,
+	/* tp_as_mapping     */ 0,
+	/* tp_hash           */ (hashfunc)0,
+	/* tp_call           */ (ternaryfunc)ib_call,
+	/* tp_str            */ (reprfunc)0,
+        /* tp_getattro       */ (getattrofunc)0,
+        /* tp_setattro       */ (setattrofunc)0,
+        /* tp_as_buffer      */ 0,
+        /* tp_flags          */ Py_TPFLAGS_DEFAULT
+				| Py_TPFLAGS_BASETYPE ,
+	/* tp_doc */ "Interface base type providing __call__ and __adapt__",
+        /* tp_traverse       */ (traverseproc)0,
+        /* tp_clear          */ (inquiry)0,
+        /* tp_richcompare    */ (richcmpfunc)0,
+        /* tp_weaklistoffset */ (long)0,
+        /* tp_iter           */ (getiterfunc)0,
+        /* tp_iternext       */ (iternextfunc)0,
+        /* tp_methods        */ ib_methods,
+};
+
+/* ========== End: __call__ and __adapt__ descriptors =============== */
+
 static struct PyMethodDef m_methods[] = {
   {"implementedBy", (PyCFunction)implementedBy, METH_O,
    "Interfaces implemented by a class or factory.\n"
@@ -517,13 +726,16 @@
   DEFINE_STRING(__provides__);
   DEFINE_STRING(__class__);
   DEFINE_STRING(__providedBy__);
-  DEFINE_STRING(isOrExtends);
   DEFINE_STRING(extends);
   DEFINE_STRING(_implied);
   DEFINE_STRING(_implements);
   DEFINE_STRING(_cls);
+  DEFINE_STRING(__conform__);
+  DEFINE_STRING(_call_conform);
 #undef DEFINE_STRING
-  
+  adapter_hooks = PyList_New(0);
+  if (adapter_hooks == NULL)
+    return;
         
   /* Initialize types: */
   SpecType.tp_new = PyBaseObject_Type.tp_new;
@@ -535,6 +747,11 @@
   CPBType.tp_new = PyBaseObject_Type.tp_new;
   if (PyType_Ready(&CPBType) < 0)
     return;
+
+  InterfaceBase.tp_new = PyBaseObject_Type.tp_new;
+  if (PyType_Ready(&InterfaceBase) < 0)
+    return;
+
   
   /* Create the module and add the functions */
   m = Py_InitModule3("_zope_interface_coptimizations", m_methods,
@@ -551,4 +768,8 @@
     return;
   if (PyModule_AddObject(m, "ClassProvidesBase", (PyObject *)&CPBType) < 0)
     return;
+  if (PyModule_AddObject(m, "InterfaceBase", (PyObject *)&InterfaceBase) < 0)
+    return;
+  if (PyModule_AddObject(m, "adapter_hooks", adapter_hooks) < 0)
+    return;
 }

Modified: Zope3/trunk/src/zope/interface/interface.py
===================================================================
--- Zope3/trunk/src/zope/interface/interface.py	2006-04-30 13:45:31 UTC (rev 67760)
+++ Zope3/trunk/src/zope/interface/interface.py	2006-04-30 13:56:44 UTC (rev 67761)
@@ -150,15 +150,56 @@
         """
         return interface in self._implied
 
+    __call__ = isOrExtends
+
 SpecificationBase = SpecificationBasePy
 
+_marker = object()
+class InterfaceBasePy(object):
+    """Base class that wants to be replaced with a C base :)
+    """
+
+    def __call__(self, obj, alternate=_marker):
+        """Adapt an object to the interface
+        """
+        conform = getattr(obj, '__conform__', None)
+        if conform is not None:
+            adapter = self._call_conform(conform)
+            if adapter is not None:
+                return adapter
+
+        adapter = self.__adapt__(obj)
+
+        if adapter is not None:
+            return adapter
+        elif alternate is not _marker:
+            return alternate
+        else:
+            raise TypeError("Could not adapt", obj, self)
+
+    def __adapt__(self, obj):
+        """Adapt an object to the reciever
+        """
+        if self.providedBy(obj):
+            return obj
+
+        for hook in adapter_hooks:
+            adapter = hook(self, obj)
+            if adapter is not None:
+                return adapter
+    
+InterfaceBase = InterfaceBasePy
+
+adapter_hooks = []
+
 try:
-    from _zope_interface_coptimizations import SpecificationBase
+    import _zope_interface_coptimizations
 except ImportError:
     pass
+else:
+    from _zope_interface_coptimizations import SpecificationBase
+    from _zope_interface_coptimizations import InterfaceBase, adapter_hooks
 
-
-
 class Specification(SpecificationBase):
     """Specifications
 
@@ -390,7 +431,7 @@
         else:
             return attr
 
-class InterfaceClass(Element, Specification):
+class InterfaceClass(Element, InterfaceBase, Specification):
     """Prototype (scarecrow) Interfaces Implementation."""
 
     # We can't say this yet because we don't have enough
@@ -589,169 +630,25 @@
             self._v_repr = r
             return r
 
-    def __call__():
-        # Mind the closure. It serves to keep a unique marker around to
-        # allow for an optional argument to __call__ without resorting
-        # to a global marker.
-        #
-        # This provides some consistency with the PEP 246 adapt method.
+    def _call_conform(self, conform):
+        try:
+            return conform(self)
+        except TypeError:
+            # We got a TypeError. It might be an error raised by
+            # the __conform__ implementation, or *we* may have
+            # made the TypeError by calling an unbound method
+            # (object is a class).  In the later case, we behave
+            # as though there is no __conform__ method. We can
+            # detect this case by checking whether there is more
+            # than one traceback object in the traceback chain:
+            if sys.exc_info()[2].tb_next is not None:
+                # There is more than one entry in the chain, so
+                # reraise the error:
+                raise
+            # This clever trick is from Phillip Eby
 
-        marker = object()
+        return None
 
-        def __call__(self, obj, alternate=marker):
-            """Adapt an object to the interface
-
-               The sematics based on those of the PEP 246 adapt function.
-
-               If an object cannot be adapted, then a TypeError is raised::
-
-                 >>> import zope.interface
-                 >>> class I(zope.interface.Interface):
-                 ...     pass
-
-                 >>> I(0)
-                 Traceback (most recent call last):
-                 ...
-                 TypeError: ('Could not adapt', 0, """ \
-                      """<InterfaceClass zope.interface.interface.I>)
-
-               unless an alternate value is provided as a second
-               positional argument::
-
-                 >>> I(0, 'bob')
-                 'bob'
-
-               If an object already implements the interface, then it will be
-               returned::
-
-                 >>> class C(object):
-                 ...     zope.interface.implements(I)
-
-                 >>> obj = C()
-                 >>> I(obj) is obj
-                 True
-
-               If an object implements __conform__, then it will be used::
-
-                 >>> class C(object):
-                 ...     zope.interface.implements(I)
-                 ...     def __conform__(self, proto):
-                 ...          return 0
-
-                 >>> I(C())
-                 0
-
-               Adapter hooks (see __adapt__) will also be used, if present:
-
-                 >>> from zope.interface.interface import adapter_hooks
-                 >>> def adapt_0_to_42(iface, obj):
-                 ...     if obj == 0:
-                 ...         return 42
-
-                 >>> adapter_hooks.append(adapt_0_to_42)
-                 >>> I(0)
-                 42
-
-                 >>> adapter_hooks.remove(adapt_0_to_42)
-                 >>> I(0)
-                 Traceback (most recent call last):
-                 ...
-                 TypeError: ('Could not adapt', 0, """ \
-                      """<InterfaceClass zope.interface.interface.I>)
-
-            """
-            conform = getattr(obj, '__conform__', None)
-            if conform is not None:
-                try:
-                    adapter = conform(self)
-                except TypeError:
-                    # We got a TypeError. It might be an error raised by
-                    # the __conform__ implementation, or *we* may have
-                    # made the TypeError by calling an unbound method
-                    # (object is a class).  In the later case, we behave
-                    # as though there is no __conform__ method. We can
-                    # detect this case by checking whether there is more
-                    # than one traceback object in the traceback chain:
-                    if sys.exc_info()[2].tb_next is not None:
-                        # There is more than one entry in the chain, so
-                        # reraise the error:
-                        raise
-                    # This clever trick is from Phillip Eby
-                else:
-                    if adapter is not None:
-                        return adapter
-
-            adapter = self.__adapt__(obj)
-
-            if adapter is not None:
-                return adapter
-            elif alternate is not marker:
-                return alternate
-            else:
-                raise TypeError("Could not adapt", obj, self)
-
-        return __call__
-
-    __call__ = __call__() # Make the closure the *real* __call__ method.
-
-    def __adapt__(self, obj):
-        """Adapt an object to the reciever
-
-           This method is normally not called directly. It is called by
-           the PEP 246 adapt framework and by the interface __call__
-           operator.
-
-           The adapt method is responsible for adapting an object to
-           the reciever.
-
-           The default version returns None::
-
-             >>> import zope.interface
-             >>> class I(zope.interface.Interface):
-             ...     pass
-
-             >>> I.__adapt__(0)
-
-           unless the object given provides the interface::
-
-             >>> class C(object):
-             ...     zope.interface.implements(I)
-
-             >>> obj = C()
-             >>> I.__adapt__(obj) is obj
-             True
-
-           Adapter hooks can be provided (or removed) to provide custom
-           adaptation. We'll install a silly hook that adapts 0 to 42.
-           We install a hook by simply adding it to the adapter_hooks
-           list::
-
-             >>> from zope.interface.interface import adapter_hooks
-             >>> def adapt_0_to_42(iface, obj):
-             ...     if obj == 0:
-             ...         return 42
-
-             >>> adapter_hooks.append(adapt_0_to_42)
-             >>> I.__adapt__(0)
-             42
-
-           Hooks must either return an adapter, or None if no adapter can
-           be found.
-
-           Hooks can be uninstalled by removing them from the list::
-
-             >>> adapter_hooks.remove(adapt_0_to_42)
-             >>> I.__adapt__(0)
-
-           """
-        if self.providedBy(obj):
-            return obj
-
-        for hook in adapter_hooks:
-            adapter = hook(self, obj)
-            if adapter is not None:
-                return adapter
-
     def __reduce__(self):
         return self.__name__
 
@@ -803,8 +700,6 @@
         return c > 0
 
 
-adapter_hooks = []
-
 Interface = InterfaceClass("Interface", __module__ = 'zope.interface')
 
 class Attribute(Element):

Modified: Zope3/trunk/src/zope/interface/tests/test_interface.py
===================================================================
--- Zope3/trunk/src/zope/interface/tests/test_interface.py	2006-04-30 13:45:31 UTC (rev 67760)
+++ Zope3/trunk/src/zope/interface/tests/test_interface.py	2006-04-30 13:56:44 UTC (rev 67761)
@@ -327,7 +327,6 @@
 
 """
 
-
 def test_suite():
     from zope.testing import doctest
     suite = unittest.makeSuite(InterfaceTests)

Modified: Zope3/trunk/src/zope/security/checker.py
===================================================================
--- Zope3/trunk/src/zope/security/checker.py	2006-04-30 13:45:31 UTC (rev 67760)
+++ Zope3/trunk/src/zope/security/checker.py	2006-04-30 13:56:44 UTC (rev 67761)
@@ -647,6 +647,7 @@
     _implied=CheckerPublic,
     subscribe=CheckerPublic,
     unsubscribe=CheckerPublic,
+    __call__=CheckerPublic,
     )
 
 def f():



More information about the Zope3-Checkins mailing list