[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