summaryrefslogtreecommitdiff
path: root/src/zope/interface/declarations.py
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-03-05 14:38:39 -0600
committerJason Madden <jamadden@gmail.com>2020-03-15 09:56:14 -0500
commit024f6432270afd021da2d9fff5c3f496f788e54d (patch)
treed9732ae94de818f2e3ea8ac144e1b932cbefa133 /src/zope/interface/declarations.py
parent354faccebd5b612a2ac8e081a7e5d2f7fb1089c1 (diff)
downloadzope-interface-issue21.tar.gz
Use C3 (mostly) to compute IRO.issue21
Fixes #21 The 'mostly' is because interfaces are used in cases that C3 forbids; when there's a conflict, we fallback to the legacy algorithm. It turns out there are few conflicts (13K out of 149K total orderings in Plone). I hoped the fix for #8 might shake out automatically, but it didn't. Optimize the extremely common case of a __bases__ of length one. In the benchmark, 4/5 of the interfaces and related objects have a base of length one. Fix the bad IROs in the bundled ABC interfaces, and implement a way to get warnings or errors. In running plone/buildout.coredev and tracking the RO requests, the stats for equal, not equal, and inconsistent-so-fallback, I got {'ros': 148868, 'eq': 138461, 'ne': 10407, 'inconsistent': 12934} Add the interface module to the Attribute str. This was extremely helpful tracking down the Plone problem; IDate is defined in multiple modules.
Diffstat (limited to 'src/zope/interface/declarations.py')
-rw-r--r--src/zope/interface/declarations.py86
1 files changed, 72 insertions, 14 deletions
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py
index 9c15b9b..1e9a2ea 100644
--- a/src/zope/interface/declarations.py
+++ b/src/zope/interface/declarations.py
@@ -449,35 +449,79 @@ def classImplementsOnly(cls, *interfaces):
def classImplements(cls, *interfaces):
- """Declare additional interfaces implemented for instances of a class
+ """
+ Declare additional interfaces implemented for instances of a class
- The arguments after the class are one or more interfaces or
- interface specifications (`~zope.interface.interfaces.IDeclaration` objects).
+ The arguments after the class are one or more interfaces or
+ interface specifications (`~zope.interface.interfaces.IDeclaration` objects).
- The interfaces given (including the interfaces in the specifications)
- are added to any interfaces previously declared.
+ The interfaces given (including the interfaces in the specifications)
+ are added to any interfaces previously declared. An effort is made to
+ keep a consistent C3 resolution order, but this cannot be guaranteed.
+
+ .. versionchanged:: 5.0.0
+ Each individual interface in *interfaces* may be added to either the
+ beginning or end of the list of interfaces declared for *cls*,
+ based on inheritance, in order to try to maintain a consistent
+ resolution order. Previously, all interfaces were added to the end.
"""
spec = implementedBy(cls)
- spec.declared += tuple(_normalizeargs(interfaces))
+ interfaces = tuple(_normalizeargs(interfaces))
+
+ before = []
+ after = []
+
+ # Take steps to try to avoid producing an invalid resolution
+ # order, while still allowing for BWC (in the past, we always
+ # appended)
+ for iface in interfaces:
+ for b in spec.declared:
+ if iface.extends(b):
+ before.append(iface)
+ break
+ else:
+ after.append(iface)
+ _classImplements_ordered(spec, tuple(before), tuple(after))
- # compute the bases
- bases = []
- seen = {}
- for b in spec.declared:
+
+def classImplementsFirst(cls, iface):
+ """
+ Declare that instances of *cls* additionally provide *iface*.
+
+ The second argument is an interface or interface specification.
+ It is added as the highest priority (first in the IRO) interface;
+ no attempt is made to keep a consistent resolution order.
+
+ .. versionadded:: 5.0.0
+ """
+ spec = implementedBy(cls)
+ _classImplements_ordered(spec, (iface,), ())
+
+
+def _classImplements_ordered(spec, before=(), after=()):
+ # eliminate duplicates
+ new_declared = []
+ seen = set()
+ for b in before + spec.declared + after:
if b not in seen:
- seen[b] = 1
- bases.append(b)
+ new_declared.append(b)
+ seen.add(b)
- if spec.inherit is not None:
+ spec.declared = tuple(new_declared)
+
+ # compute the bases
+ bases = new_declared # guaranteed no dupes
+ if spec.inherit is not None:
for c in spec.inherit.__bases__:
b = implementedBy(c)
if b not in seen:
- seen[b] = 1
+ seen.add(b)
bases.append(b)
spec.__bases__ = tuple(bases)
+
def _implements_advice(cls):
interfaces, classImplements = cls.__dict__['__implements_advice_data__']
del cls.__implements_advice_data__
@@ -664,6 +708,13 @@ class Provides(Declaration): # Really named ProvidesClass
self._cls = cls
Declaration.__init__(self, *(interfaces + (implementedBy(cls), )))
+ def __repr__(self):
+ return "<%s.%s for %s>" % (
+ self.__class__.__module__,
+ self.__class__.__name__,
+ self._cls,
+ )
+
def __reduce__(self):
return Provides, self.__args
@@ -794,6 +845,13 @@ class ClassProvides(Declaration, ClassProvidesBase):
self.__args = (cls, metacls, ) + interfaces
Declaration.__init__(self, *(interfaces + (implementedBy(metacls), )))
+ def __repr__(self):
+ return "<%s.%s for %s>" % (
+ self.__class__.__module__,
+ self.__class__.__name__,
+ self._cls,
+ )
+
def __reduce__(self):
return self.__class__, self.__args