summaryrefslogtreecommitdiff
path: root/src/zope/interface/interface.py
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-04-03 09:55:43 -0500
committerJason Madden <jamadden@gmail.com>2020-04-06 09:14:45 -0500
commit10eadd6305ee57910dbcc508b293f4bf0364fd84 (patch)
tree63a450400cf79c33cb21a1a81d4c00371bf84dcc /src/zope/interface/interface.py
parent1af83ef9f90aa7a558314892b72eec6d62263981 (diff)
downloadzope-interface-issue3.tar.gz
Let interface 'subclasses' override __adapt__.issue3
Cooperate with InterfaceClass to ensure there is no performance penalty for this. Fixes #3 +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | Benchmark | bench_master38 | bench_issue3 | bench_issue3_opt | +=============================================================+================+==============================+==============================+ | call interface (provides; deep) | 369 ns | 454 ns: 1.23x slower (+23%) | not significant | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (provides; wide) | 373 ns | 457 ns: 1.22x slower (+22%) | 365 ns: 1.02x faster (-2%) | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (no alternate, no conform, not provided) | 671 ns | 760 ns: 1.13x slower (+13%) | 636 ns: 1.06x faster (-5%) | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (alternate, no conform, not provided) | 395 ns | 494 ns: 1.25x slower (+25%) | not significant | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (no alternate, valid conform, not provided) | 250 ns | not significant | 227 ns: 1.10x faster (-9%) | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+ | call interface (alternate, invalid conform, not provided) | 348 ns | 424 ns: 1.22x slower (+22%) | not significant | +-------------------------------------------------------------+----------------+------------------------------+------------------------------+
Diffstat (limited to 'src/zope/interface/interface.py')
-rw-r--r--src/zope/interface/interface.py77
1 files changed, 76 insertions, 1 deletions
diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py
index d035ade..ff26d33 100644
--- a/src/zope/interface/interface.py
+++ b/src/zope/interface/interface.py
@@ -20,6 +20,7 @@ from types import FunctionType
import weakref
from zope.interface._compat import _use_c_impl
+from zope.interface._compat import PYTHON2 as PY2
from zope.interface.exceptions import Invalid
from zope.interface.ro import ro as calculate_ro
from zope.interface import ro
@@ -36,7 +37,10 @@ __all__ = [
CO_VARARGS = 4
CO_VARKEYWORDS = 8
+# Put in the attrs dict of an interface by ``taggedValue`` and ``invariants``
TAGGED_DATA = '__interface_tagged_values__'
+# Put in the attrs dict of an interface by ``interfacemethod``
+INTERFACE_METHODS = '__interface_methods__'
_decorator_non_return = object()
_marker = object()
@@ -651,6 +655,21 @@ _InterfaceClassBase = _InterfaceMetaClass(
)
+def interfacemethod(func):
+ """
+ Convert a method specification to an actual method of the interface.
+
+ This is a decorator that functions like `staticmethod` et al.
+
+ The primary use of this decorator is to allow interface definitions to
+ define the ``__adapt__`` method.
+ """
+ f_locals = sys._getframe(1).f_locals
+ methods = f_locals.setdefault(INTERFACE_METHODS, {})
+ methods[func.__name__] = func
+ return _decorator_non_return
+
+
class InterfaceClass(_InterfaceClassBase):
"""
Prototype (scarecrow) Interfaces Implementation.
@@ -664,6 +683,57 @@ class InterfaceClass(_InterfaceClassBase):
#
#implements(IInterface)
+ def __new__(cls, name=None, bases=(), attrs=None, __doc__=None, # pylint:disable=redefined-builtin
+ __module__=None):
+ assert isinstance(bases, tuple)
+ attrs = attrs or {}
+ needs_custom_class = attrs.pop(INTERFACE_METHODS, None)
+ if needs_custom_class:
+ needs_custom_class.update(
+ {'__classcell__': attrs.pop('__classcell__')}
+ if '__classcell__' in attrs
+ else {}
+ )
+ if '__adapt__' in needs_custom_class:
+ # We need to tell the C code to call this.
+ needs_custom_class['_CALL_CUSTOM_ADAPT'] = 1
+
+ if issubclass(cls, _InterfaceClassWithCustomMethods):
+ cls_bases = (cls,)
+ elif cls is InterfaceClass:
+ cls_bases = (_InterfaceClassWithCustomMethods,)
+ else:
+ cls_bases = (cls, _InterfaceClassWithCustomMethods)
+
+ cls = type(cls)( # pylint:disable=self-cls-assignment
+ name + "<WithCustomMethods>",
+ cls_bases,
+ needs_custom_class
+ )
+ elif PY2 and bases and len(bases) > 1:
+ bases_with_custom_methods = tuple(
+ type(b)
+ for b in bases
+ if issubclass(type(b), _InterfaceClassWithCustomMethods)
+ )
+
+ # If we have a subclass of InterfaceClass in *bases*,
+ # Python 3 is smart enough to pass that as *cls*, but Python
+ # 2 just passes whatever the first base in *bases* is. This means that if
+ # we have multiple inheritance, and one of our bases has already defined
+ # a custom method like ``__adapt__``, we do the right thing automatically
+ # and extend it on Python 3, but not necessarily on Python 2. To fix this, we need
+ # to run the MRO algorithm and get the most derived base manually.
+ # Note that this only works for consistent resolution orders
+ if bases_with_custom_methods:
+ cls = type( # pylint:disable=self-cls-assignment
+ name + "<WithCustomMethods>",
+ bases_with_custom_methods,
+ {}
+ ).__mro__[1] # Not the class we created, the most derived.
+
+ return _InterfaceClassBase.__new__(cls)
+
def __init__(self, name, bases=(), attrs=None, __doc__=None, # pylint:disable=redefined-builtin
__module__=None):
# We don't call our metaclass parent directly
@@ -738,7 +808,7 @@ class InterfaceClass(_InterfaceClassBase):
# __qualname__: PEP 3155 (Python 3.3+)
'__qualname__',
# __annotations__: PEP 3107 (Python 3.0+)
- '__annotations__'
+ '__annotations__',
)
and aval is not _decorator_non_return
}
@@ -889,6 +959,11 @@ assert Interface.__sro__ == (Interface,)
Specification._ROOT = Interface
ro._ROOT = Interface
+class _InterfaceClassWithCustomMethods(InterfaceClass):
+ """
+ Marker class for interfaces with custom methods that override InterfaceClass methods.
+ """
+
class Attribute(Element):
"""Attribute descriptions