summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-03-11 12:19:19 -0500
committerJason Madden <jamadden@gmail.com>2020-03-18 12:27:20 -0500
commit413e716f9fb48338829435c4f243133589eb9fe6 (patch)
treebd00663da33f5b85af67531627f1e39941307fa6
parent7afd59d869e3391ce27a4a07ad0931eb5e7910a1 (diff)
downloadzope-interface-413e716f9fb48338829435c4f243133589eb9fe6.tar.gz
Avoid use of a metaclass by implementeng __getattribute__.
This is pretty, but it slows down all attribute access to interfaces. By up to 25%. I'm not sure that's acceptable for things like Interface.providedBy. +-------------------------------------------+------------+-------------------------------+ | Benchmark | 38-master3 | 38-faster3 | +===========================================+============+===============================+ | read __module__ | 41.1 ns | 44.3 ns: 1.08x slower (+8%) | +-------------------------------------------+------------+-------------------------------+ | read __name__ | 41.3 ns | 51.6 ns: 1.25x slower (+25%) | +-------------------------------------------+------------+-------------------------------+ | read __doc__ | 41.8 ns | 53.3 ns: 1.28x slower (+28%) | +-------------------------------------------+------------+-------------------------------+ | read providedBy | 56.7 ns | 71.6 ns: 1.26x slower (+26%) | +-------------------------------------------+------------+-------------------------------+ | query adapter (no registrations) | 3.85 ms | 2.95 ms: 1.31x faster (-23%) | +-------------------------------------------+------------+-------------------------------+ | query adapter (all trivial registrations) | 4.59 ms | 3.65 ms: 1.26x faster (-20%) | +-------------------------------------------+------------+-------------------------------+ | contains (empty dict) | 136 ns | 55.4 ns: 2.45x faster (-59%) | +-------------------------------------------+------------+-------------------------------+ | contains (populated dict) | 137 ns | 55.0 ns: 2.49x faster (-60%) | +-------------------------------------------+------------+-------------------------------+ | contains (populated list) | 40.2 us | 2.95 us: 13.62x faster (-93%) | +-------------------------------------------+------------+-------------------------------+
-rw-r--r--benchmarks/micro.py50
-rw-r--r--src/zope/interface/_zope_interface_coptimizations.c63
-rw-r--r--src/zope/interface/declarations.py72
-rw-r--r--src/zope/interface/interface.py294
-rw-r--r--src/zope/interface/tests/test_interface.py4
5 files changed, 270 insertions, 213 deletions
diff --git a/benchmarks/micro.py b/benchmarks/micro.py
index 9031261..779298c 100644
--- a/benchmarks/micro.py
+++ b/benchmarks/micro.py
@@ -27,7 +27,7 @@ providers = [
for implementer in implementers
]
-INNER = 10
+INNER = 100
def bench_in(loops, o):
t0 = pyperf.perf_counter()
@@ -50,8 +50,56 @@ def bench_query_adapter(loops, components):
components.queryAdapter(provider, iface)
return pyperf.perf_counter() - t0
+def bench_getattr(loops, name, get=getattr):
+ t0 = pyperf.perf_counter()
+ for _ in range(loops):
+ for _ in range(INNER):
+ get(Interface, name) # 1
+ get(Interface, name) # 2
+ get(Interface, name) # 3
+ get(Interface, name) # 4
+ get(Interface, name) # 5
+ get(Interface, name) # 6
+ get(Interface, name) # 7
+ get(Interface, name) # 8
+ get(Interface, name) # 9
+ get(Interface, name) # 10
+ return pyperf.perf_counter() - t0
+
runner = pyperf.Runner()
+# TODO: Need benchmarks of adaptation, etc, using interface inheritance.
+# TODO: Need benchmarks of sorting (e.g., putting in a BTree)
+# TODO: Need those same benchmarks for implementedBy/Implements objects.
+
+runner.bench_time_func(
+ 'read __module__', # stored in C, accessed through __getattribute__
+ bench_getattr,
+ '__module__',
+ inner_loops=INNER * 10
+)
+
+runner.bench_time_func(
+ 'read __name__', # stored in C, accessed through PyMemberDef
+ bench_getattr,
+ '__name__',
+ inner_loops=INNER * 10
+)
+
+runner.bench_time_func(
+ 'read __doc__', # stored in Python instance dictionary directly
+ bench_getattr,
+ '__doc__',
+ inner_loops=INNER * 10
+)
+
+runner.bench_time_func(
+ 'read providedBy', # from the class, wrapped into a method object.
+ bench_getattr,
+ 'providedBy',
+ inner_loops=INNER * 10
+)
+
runner.bench_time_func(
'query adapter (no registrations)',
bench_query_adapter,
diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c
index 7341474..adc7351 100644
--- a/src/zope/interface/_zope_interface_coptimizations.c
+++ b/src/zope/interface/_zope_interface_coptimizations.c
@@ -47,7 +47,8 @@ static PyObject *str_uncached_lookup, *str_uncached_lookupAll;
static PyObject *str_uncached_subscriptions;
static PyObject *str_registry, *strro, *str_generation, *strchanged;
static PyObject *str__self__;
-
+static PyObject *str__module__;
+static PyObject *str__name__;
static PyTypeObject *Implements;
@@ -97,7 +98,7 @@ import_declarations(void)
}
-static PyTypeObject SpecType; /* Forward */
+static PyTypeObject SpecificationBaseType; /* Forward */
static PyObject *
implementedByFallback(PyObject *cls)
@@ -177,7 +178,7 @@ getObjectSpecification(PyObject *ignored, PyObject *ob)
PyObject *cls, *result;
result = PyObject_GetAttr(ob, str__provides__);
- if (result != NULL && PyObject_TypeCheck(result, &SpecType))
+ if (result != NULL && PyObject_TypeCheck(result, &SpecificationBaseType))
return result;
@@ -225,7 +226,7 @@ providedBy(PyObject *ignored, PyObject *ob)
because we may have a proxy, so we'll just try to get the
only attribute.
*/
- if (PyObject_TypeCheck(result, &SpecType)
+ if (PyObject_TypeCheck(result, &SpecificationBaseType)
||
PyObject_HasAttr(result, strextends)
)
@@ -378,7 +379,7 @@ Spec_providedBy(PyObject *self, PyObject *ob)
if (decl == NULL)
return NULL;
- if (PyObject_TypeCheck(decl, &SpecType))
+ if (PyObject_TypeCheck(decl, &SpecificationBaseType))
item = Spec_extends((Spec*)decl, self);
else
/* decl is probably a security proxy. We have to go the long way
@@ -405,7 +406,7 @@ Spec_implementedBy(PyObject *self, PyObject *cls)
if (decl == NULL)
return NULL;
- if (PyObject_TypeCheck(decl, &SpecType))
+ if (PyObject_TypeCheck(decl, &SpecificationBaseType))
item = Spec_extends((Spec*)decl, self);
else
item = PyObject_CallFunctionObjArgs(decl, self, NULL);
@@ -438,7 +439,7 @@ static PyMemberDef Spec_members[] = {
};
-static PyTypeObject SpecType = {
+static PyTypeObject SpecificationBaseType = {
PyVarObject_HEAD_INIT(NULL, 0)
/* tp_name */ "_interface_coptimizations."
"SpecificationBase",
@@ -617,7 +618,7 @@ static PyTypeObject CPBType = {
/* tp_methods */ 0,
/* tp_members */ CPB_members,
/* tp_getset */ 0,
- /* tp_base */ &SpecType,
+ /* tp_base */ &SpecificationBaseType,
/* tp_dict */ 0, /* internal use */
/* tp_descr_get */ (descrgetfunc)CPB_descr_get,
/* tp_descr_set */ 0,
@@ -654,7 +655,7 @@ __adapt__(PyObject *self, PyObject *obj)
if (decl == NULL)
return NULL;
- if (PyObject_TypeCheck(decl, &SpecType))
+ if (PyObject_TypeCheck(decl, &SpecificationBaseType))
{
PyObject *implied;
@@ -817,7 +818,6 @@ IB_dealloc(IB* self)
static PyMemberDef IB_members[] = {
{"__name__", T_OBJECT_EX, offsetof(IB, __name__), 0, ""},
- {"__module__", T_OBJECT_EX, offsetof(IB, __module__), 0, ""},
{"__ibmodule__", T_OBJECT_EX, offsetof(IB, __module__), 0, ""},
{NULL}
};
@@ -918,6 +918,7 @@ IB_richcompare(IB* self, PyObject* other, int op)
else if (result == 1) {
result = PyObject_RichCompareBool(self->__module__, othermod, op);
}
+ // If either comparison failed, we have an error set.
if (result == -1) {
goto cleanup;
}
@@ -936,18 +937,22 @@ cleanup:
}
static PyObject*
-IB_module_get(IB* self, void* context)
+IB_getattro(IB* self, PyObject* name)
{
- return self->__module__;
-}
+ int cmpresult;
+ cmpresult = PyObject_RichCompareBool(name, str__module__, Py_EQ);
+ if (cmpresult == -1)
+ return NULL;
-static int
-IB_module_set(IB* self, PyObject* value, void* context)
-{
- Py_XINCREF(value);
- Py_XDECREF(self->__module__);
- self->__module__ = value;
- return 0;
+ if (cmpresult) { // __module__
+ if (self->__module__) {
+ Py_INCREF(self->__module__);
+ return self->__module__;
+ }
+ }
+
+ // Wasn't __module__, *or* it was, but it was unset.
+ return PyObject_GenericGetAttr(OBJECT(self), name);
}
static int
@@ -961,10 +966,6 @@ IB_init(IB* self, PyObject* args, PyObject* kwargs)
return 0;
}
-static PyGetSetDef IB_getsets[] = {
- {"__module__", (getter)IB_module_get, (setter)IB_module_set, 0, NULL},
- {NULL}
-};
static PyTypeObject InterfaceBaseType = {
PyVarObject_HEAD_INIT(NULL, 0)
@@ -984,7 +985,7 @@ static PyTypeObject InterfaceBaseType = {
/* tp_hash */ (hashfunc)IB_hash,
/* tp_call */ (ternaryfunc)ib_call,
/* tp_str */ (reprfunc)0,
- /* tp_getattro */ (getattrofunc)0,
+ /* tp_getattro */ (getattrofunc)IB_getattro,
/* tp_setattro */ (setattrofunc)0,
/* tp_as_buffer */ 0,
/* tp_flags */ Py_TPFLAGS_DEFAULT
@@ -998,8 +999,8 @@ static PyTypeObject InterfaceBaseType = {
/* tp_iternext */ (iternextfunc)0,
/* tp_methods */ ib_methods,
/* tp_members */ IB_members,
- /* tp_getset */ IB_getsets,
- /* tp_base */ &SpecType,
+ /* tp_getset */ 0,
+ /* tp_base */ &SpecificationBaseType,
/* tp_dict */ 0,
/* tp_descr_get */ 0,
/* tp_descr_set */ 0,
@@ -1958,14 +1959,16 @@ init(void)
DEFINE_STRING(ro);
DEFINE_STRING(changed);
DEFINE_STRING(__self__);
+ DEFINE_STRING(__name__);
+ DEFINE_STRING(__module__);
#undef DEFINE_STRING
adapter_hooks = PyList_New(0);
if (adapter_hooks == NULL)
return NULL;
/* Initialize types: */
- SpecType.tp_new = PyBaseObject_Type.tp_new;
- if (PyType_Ready(&SpecType) < 0)
+ SpecificationBaseType.tp_new = PyBaseObject_Type.tp_new;
+ if (PyType_Ready(&SpecificationBaseType) < 0)
return NULL;
OSDType.tp_new = PyBaseObject_Type.tp_new;
if (PyType_Ready(&OSDType) < 0)
@@ -1997,7 +2000,7 @@ init(void)
return NULL;
/* Add types: */
- if (PyModule_AddObject(m, "SpecificationBase", OBJECT(&SpecType)) < 0)
+ if (PyModule_AddObject(m, "SpecificationBase", OBJECT(&SpecificationBaseType)) < 0)
return NULL;
if (PyModule_AddObject(m, "ObjectSpecificationDescriptor",
(PyObject *)&OSDType) < 0)
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py
index 1e9a2ea..502e26e 100644
--- a/src/zope/interface/declarations.py
+++ b/src/zope/interface/declarations.py
@@ -36,6 +36,7 @@ from zope.interface.advice import addClassAdvisor
from zope.interface.interface import InterfaceClass
from zope.interface.interface import SpecificationBase
from zope.interface.interface import Specification
+from zope.interface.interface import NameAndModuleComparisonMixin
from zope.interface._compat import CLASS_TYPES as DescriptorAwareMetaClasses
from zope.interface._compat import PYTHON3
from zope.interface._compat import _use_c_impl
@@ -197,7 +198,29 @@ class _ImmutableDeclaration(Declaration):
#
# These specify interfaces implemented by instances of classes
-class Implements(Declaration):
+class Implements(NameAndModuleComparisonMixin,
+ Declaration):
+ # Inherit from NameAndModuleComparisonMixin to be
+ # mutually comparable with InterfaceClass objects.
+ # (The two must be mutually comparable to be able to work in e.g., BTrees.)
+ # Instances of this class generally don't have a __module__ other than
+ # `zope.interface.declarations`, whereas they *do* have a __name__ that is the
+ # fully qualified name of the object they are representing.
+
+ # Note, though, that equality and hashing are still identity based. This
+ # accounts for things like nested objects that have the same name (typically
+ # only in tests) and is consistent with pickling. As far as comparisons to InterfaceClass
+ # goes, we'll never have equal name and module to those, so we're still consistent there.
+ # Instances of this class are essentially intended to be unique and are
+ # heavily cached (note how our __reduce__ handles this) so having identity
+ # based hash and eq should also work.
+
+ # We want equality and hashing to be based on identity. However, we can't actually
+ # implement __eq__/__ne__ to do this because sometimes we get wrapped in a proxy.
+ # We need to let the proxy types implement these methods so they can handle unwrapping
+ # and then rely on: (1) the interpreter automatically changing `implements == proxy` into
+ # `proxy == implements` (which will call proxy.__eq__ to do the unwrapping) and then
+ # (2) the default equality and hashing semantics being identity based.
# class whose specification should be used as additional base
inherit = None
@@ -233,53 +256,6 @@ class Implements(Declaration):
def __reduce__(self):
return implementedBy, (self.inherit, )
- def __cmp(self, other):
- # Yes, I did mean to name this __cmp, rather than __cmp__.
- # It is a private method used by __lt__ and __gt__.
- # This is based on, and compatible with, InterfaceClass.
- # (The two must be mutually comparable to be able to work in e.g., BTrees.)
- # Instances of this class generally don't have a __module__ other than
- # `zope.interface.declarations`, whereas they *do* have a __name__ that is the
- # fully qualified name of the object they are representing.
-
- # Note, though, that equality and hashing are still identity based. This
- # accounts for things like nested objects that have the same name (typically
- # only in tests) and is consistent with pickling. As far as comparisons to InterfaceClass
- # goes, we'll never have equal name and module to those, so we're still consistent there.
- # Instances of this class are essentially intended to be unique and are
- # heavily cached (note how our __reduce__ handles this) so having identity
- # based hash and eq should also work.
- if other is None:
- return -1
-
- n1 = (self.__name__, self.__module__)
- n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', ''))
-
- # This spelling works under Python3, which doesn't have cmp().
- return (n1 > n2) - (n1 < n2)
-
- # We want equality and hashing to be based on identity. However, we can't actually
- # implement __eq__/__ne__ to do this because sometimes we get wrapped in a proxy.
- # We need to let the proxy types implement these methods so they can handle unwrapping
- # and then rely on: (1) the interpreter automatically changing `implements == proxy` into
- # `proxy == implements` (which will call proxy.__eq__ to do the unwrapping) and then
- # (2) the default equality and hashing semantics being identity based.
-
- def __lt__(self, other):
- c = self.__cmp(other)
- return c < 0
-
- def __le__(self, other):
- c = self.__cmp(other)
- return c <= 0
-
- def __gt__(self, other):
- c = self.__cmp(other)
- return c > 0
-
- def __ge__(self, other):
- c = self.__cmp(other)
- return c >= 0
def _implements_name(ob):
# Return the __name__ attribute to be used by its __implemented__
diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py
index 1ed1b6f..d17c8dc 100644
--- a/src/zope/interface/interface.py
+++ b/src/zope/interface/interface.py
@@ -13,14 +13,13 @@
##############################################################################
"""Interface object implementation
"""
-
+# pylint:disable=protected-access
import sys
from types import MethodType
from types import FunctionType
import weakref
from zope.interface._compat import _use_c_impl
-from zope.interface._compat import PYTHON3 as PY3
from zope.interface.exceptions import Invalid
from zope.interface.ro import ro as calculate_ro
from zope.interface import ro
@@ -70,7 +69,7 @@ class Element(object):
#
#implements(IElement)
- def __init__(self, __name__, __doc__=''):
+ def __init__(self, __name__, __doc__=''): # pylint:disable=redefined-builtin
if not __doc__ and __name__.find(' ') >= 0:
__doc__ = __name__
__name__ = None
@@ -121,6 +120,9 @@ class Element(object):
getDirectTaggedValueTags = getTaggedValueTags
+SpecificationBasePy = object # filled by _use_c_impl.
+
+
@_use_c_impl
class SpecificationBase(object):
# This object is the base of the inheritance hierarchy for ClassProvides:
@@ -163,31 +165,126 @@ class SpecificationBase(object):
def isOrExtends(self, interface):
"""Is the interface the same as or extend the given interface
"""
- return interface in self._implied
+ return interface in self._implied # pylint:disable=no-member
__call__ = isOrExtends
+class NameAndModuleComparisonMixin(object):
+ # Internal use. Implement the basic sorting operators (but not (in)equality
+ # or hashing). Subclasses must provide ``__name__`` and ``__module__``
+ # attributes. Subclasses will be mutually comparable; but because equality
+ # and hashing semantics are missing from this class, take care in how
+ # you define those two attributes: If you stick with the default equality
+ # and hashing (identity based) you should make sure that all possible ``__name__``
+ # and ``__module__`` pairs are unique ACROSS ALL SUBCLASSES. (Actually, pretty
+ # much the same thing goes if you define equality and hashing to be based on
+ # those two attributes: they must still be consistent ACROSS ALL SUBCLASSES.)
+
+ # pylint:disable=assigning-non-slot
+ __slots__ = ()
+ def _compare(self, other):
+ """
+ Compare *self* to *other* based on ``__name__`` and ``__module__``.
+
+ Return 0 if they are equal, return 1 if *self* is
+ greater than *other*, and return -1 if *self* is less than
+ *other*.
+
+ If *other* does not have ``__name__`` or ``__module__``, then
+ return ``NotImplemented``.
+
+
+ None is treated as a pseudo interface that implies the loosest
+ contact possible, no contract. For that reason, all interfaces
+ sort before None.
+ """
+ if other is self:
+ return 0
+
+ if other is None:
+ return -1
+
+ n1 = (self.__name__, self.__module__)
+ try:
+ n2 = (other.__name__, other.__module__)
+ except AttributeError:
+ return NotImplemented
+
+ # This spelling works under Python3, which doesn't have cmp().
+ return (n1 > n2) - (n1 < n2)
+
+ def __lt__(self, other):
+ c = self._compare(other)
+ if c is NotImplemented:
+ return c
+ return c < 0
+
+ def __le__(self, other):
+ c = self._compare(other)
+ if c is NotImplemented:
+ return c
+ return c <= 0
+
+ def __gt__(self, other):
+ c = self._compare(other)
+ if c is NotImplemented:
+ return c
+ return c > 0
+
+ def __ge__(self, other):
+ c = self._compare(other)
+ if c is NotImplemented:
+ return c
+ return c >= 0
+
@_use_c_impl
-class InterfaceBase(object):
+class InterfaceBase(NameAndModuleComparisonMixin, SpecificationBasePy):
"""Base class that wants to be replaced with a C base :)
"""
- __slots__ = ()
+ __slots__ = (
+ '__name__',
+ '__ibmodule__',
+ )
- def __init__(self):
- self.__name__ = None
- self.__module__ = None
+ def __init__(self, name=None, module=None):
+ # pylint:disable=assigning-non-slot
+ self.__name__ = name
+ # We store the module value in ``__ibmodule__`` and provide access
+ # to it under ``__module__`` through ``__getattribute__``. This is
+ # because we want to store __module__ in the C structure (for
+ # speed of equality and sorting), but it's very hard to do
+ # that any other way. Using PyMemberDef or PyGetSetDef (the C
+ # versions of properties) doesn't work without adding
+ # metaclasses: creating a new subclass puts a ``__module__``
+ # string in the class dict that overrides the descriptor that
+ # would access the C structure data.
+ #
+ # We could use a metaclass to override this behaviour, but it's probably
+ # safer to use ``__getattribute__``.
+ #
+ # Setting ``__module__`` after construction is undefined.
+ # There are numerous things that cache that value directly or
+ # indirectly (and long have).
+ self.__ibmodule__ = module
def _call_conform(self, conform):
raise NotImplementedError
+ def __getattribute__(self, name):
+ if name == '__module__':
+ return self.__ibmodule__
+ return object.__getattribute__(self, name)
+
def __call__(self, obj, alternate=_marker):
"""Adapt an object to the interface
"""
try:
conform = getattr(obj, '__conform__', None)
- except:
+ # XXX: Do we really want to catch BaseException? Shouldn't
+ # things like MemoryError, KeyboardInterrupt, etc, get through?
+ except: # pylint:disable=bare-except
conform = None
if conform is not None:
adapter = self._call_conform(conform)
@@ -198,13 +295,12 @@ class InterfaceBase(object):
if adapter is not None:
return adapter
- elif alternate is not _marker:
+ if alternate is not _marker:
return alternate
- else:
- raise TypeError("Could not adapt", obj, self)
+ raise TypeError("Could not adapt", obj, self)
def __adapt__(self, obj):
- """Adapt an object to the reciever
+ """Adapt an object to the receiver
"""
if self.providedBy(obj):
return obj
@@ -214,38 +310,10 @@ class InterfaceBase(object):
if adapter is not None:
return adapter
- def __cmp(self, other):
- # Yes, I did mean to name this __cmp, rather than __cmp__.
- # It is a private method used by __lt__ and __gt__.
- # I don't want to override __eq__ because I want the default
- # __eq__, which is really fast.
- """Make interfaces sortable
-
- TODO: It would ne nice if:
-
- More specific interfaces should sort before less specific ones.
- Otherwise, sort on name and module.
-
- But this is too complicated, and we're going to punt on it
- for now.
-
- For now, sort on interface and module name.
-
- None is treated as a pseudo interface that implies the loosest
- contact possible, no contract. For that reason, all interfaces
- sort before None.
-
- """
- if other is None:
- return -1
-
- n1 = (self.__name__, self.__module__)
- n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', ''))
-
- # This spelling works under Python3, which doesn't have cmp().
- return (n1 > n2) - (n1 < n2)
+ return None
def __hash__(self):
+ # pylint:disable=assigning-non-slot,attribute-defined-outside-init
try:
return self._v_cached_hash
except AttributeError:
@@ -253,28 +321,19 @@ class InterfaceBase(object):
return self._v_cached_hash
def __eq__(self, other):
- c = self.__cmp(other)
+ c = self._compare(other)
+ if c is NotImplemented:
+ return c
return c == 0
def __ne__(self, other):
- c = self.__cmp(other)
- return c != 0
-
- def __lt__(self, other):
- c = self.__cmp(other)
- return c < 0
-
- def __le__(self, other):
- c = self.__cmp(other)
- return c <= 0
-
- def __gt__(self, other):
- c = self.__cmp(other)
- return c > 0
+ if other is self:
+ return False
- def __ge__(self, other):
- c = self.__cmp(other)
- return c >= 0
+ c = self._compare(other)
+ if c is NotImplemented:
+ return c
+ return c != 0
adapter_hooks = _use_c_impl([], 'adapter_hooks')
@@ -486,56 +545,26 @@ class Specification(SpecificationBase):
return default if attr is None else attr
-class _ModuleDescriptor(str):
- # type.__repr__ accesses self.__dict__['__module__']
- # and checks to see if it's a native string. If it's not,
- # the repr just uses the __name__. So for things to work out nicely
- # it's best for us to subclass str.
- if PY3:
- # Python 2 doesn't allow non-empty __slots__ for str
- # subclasses.
- __slots__ = ('_saved',)
-
- def __init__(self, saved):
- str.__init__(self)
- self._saved = saved
-
- def __get__(self, inst, kind):
- if inst is None:
- return self._saved
- return inst.__ibmodule__
-
- def __set__(self, inst, val):
- inst.__ibmodule__ = val
-
- def __str__(self):
- return self._saved
-
-# The simple act of having *any* metaclass besides type
-# makes our __module__ shenanigans work. Doing this at the class level,
-# and manually copying it around doesn't work.
-class _MC(type):
- def __new__(cls, name, bases, attrs):
- attrs['__module__'] = _ModuleDescriptor(attrs['__module__'])
- return type.__new__(cls, name, bases, attrs)
-
-_InterfaceClassBase = _MC(
- 'InterfaceClass',
- (Element, InterfaceBase, Specification),
- {'__module__': __name__, '__qualname__': __name__ + 'InterfaceClass'})
+class InterfaceClass(InterfaceBase, Element, Specification):
+ """
+ Prototype (scarecrow) Interfaces Implementation.
-class InterfaceClass(_InterfaceClassBase):
- """Prototype (scarecrow) Interfaces Implementation."""
+ Note that it is not possible to change the ``__name__`` or ``__module__``
+ after an instance of this object has been constructed.
+ """
# We can't say this yet because we don't have enough
# infrastructure in place.
#
#implements(IInterface)
- def __init__(self, name, bases=(), attrs=None, __doc__=None,
+ def __init__(self, name, bases=(), attrs=None, __doc__=None, # pylint:disable=redefined-builtin
__module__=None):
+ if not all(isinstance(base, InterfaceClass) for base in bases):
+ raise TypeError('Expected base interfaces')
+ InterfaceBase.__init__(self)
if attrs is None:
attrs = {}
@@ -552,8 +581,7 @@ class InterfaceClass(_InterfaceClassBase):
except (AttributeError, KeyError): # pragma: no cover
pass
- self.__module__ = __module__
- assert '__module__' not in self.__dict__
+ self.__ibmodule__ = __module__
d = attrs.get('__doc__')
if d is not None:
@@ -572,36 +600,38 @@ class InterfaceClass(_InterfaceClassBase):
for key, val in tagged_data.items():
self.setTaggedValue(key, val)
- for base in bases:
- if not isinstance(base, InterfaceClass):
- raise TypeError('Expected base interfaces')
-
-
Specification.__init__(self, bases)
+ self.__attrs = self.__compute_attrs(attrs)
+ self.__identifier__ = "%s.%s" % (__module__, name)
+
+ def __compute_attrs(self, attrs):
# Make sure that all recorded attributes (and methods) are of type
# `Attribute` and `Method`
- for name, attr in list(attrs.items()):
- if name in ('__locals__', '__qualname__', '__annotations__'):
+ def update_value(aname, aval):
+ if isinstance(aval, Attribute):
+ aval.interface = self
+ if not aval.__name__:
+ aval.__name__ = aname
+ elif isinstance(aval, FunctionType):
+ aval = fromFunction(aval, self, name=aname)
+ else:
+ raise InvalidInterface("Concrete attribute, " + aname)
+ return aval
+
+ return {
+ aname: update_value(aname, aval)
+ for aname, aval in attrs.items()
+ if aname not in (
# __locals__: Python 3 sometimes adds this.
+ '__locals__',
# __qualname__: PEP 3155 (Python 3.3+)
+ '__qualname__',
# __annotations__: PEP 3107 (Python 3.0+)
- del attrs[name]
- continue
- if isinstance(attr, Attribute):
- attr.interface = self
- if not attr.__name__:
- attr.__name__ = name
- elif isinstance(attr, FunctionType):
- attrs[name] = fromFunction(attr, self, name=name)
- elif attr is _decorator_non_return:
- del attrs[name]
- else:
- raise InvalidInterface("Concrete attribute, " + name)
-
- self.__attrs = attrs
-
- self.__identifier__ = "%s.%s" % (__module__, name)
+ '__annotations__'
+ )
+ and aval is not _decorator_non_return
+ }
def interfaces(self):
"""Return an iterator for the interfaces in the specification.
@@ -615,7 +645,7 @@ class InterfaceClass(_InterfaceClassBase):
"""Same interface or extends?"""
return self == other or other.extends(self)
- def names(self, all=False):
+ def names(self, all=False): # pylint:disable=redefined-builtin
"""Return the attribute names defined by the interface."""
if not all:
return self.__attrs.keys()
@@ -630,7 +660,7 @@ class InterfaceClass(_InterfaceClassBase):
def __iter__(self):
return iter(self.names(all=True))
- def namesAndDescriptions(self, all=False):
+ def namesAndDescriptions(self, all=False): # pylint:disable=redefined-builtin
"""Return attribute names and descriptions defined by interface."""
if not all:
return self.__attrs.items()
@@ -670,8 +700,7 @@ class InterfaceClass(_InterfaceClassBase):
except Invalid as e:
if errors is None:
raise
- else:
- errors.append(e)
+ errors.append(e)
for base in self.__bases__:
try:
base.validateInvariants(obj, errors)
@@ -717,7 +746,7 @@ class InterfaceClass(_InterfaceClassBase):
if m:
name = '%s.%s' % (m, name)
r = "<%s %s>" % (self.__class__.__name__, name)
- self._v_repr = r
+ self._v_repr = r # pylint:disable=attribute-defined-outside-init
return r
def _call_conform(self, conform):
@@ -912,6 +941,7 @@ def _wire():
classImplements(Specification, ISpecification)
# We import this here to deal with module dependencies.
+# pylint:disable=wrong-import-position
from zope.interface.declarations import implementedBy
from zope.interface.declarations import providedBy
from zope.interface.exceptions import InvalidInterface
diff --git a/src/zope/interface/tests/test_interface.py b/src/zope/interface/tests/test_interface.py
index e7ae547..076fc44 100644
--- a/src/zope/interface/tests/test_interface.py
+++ b/src/zope/interface/tests/test_interface.py
@@ -536,7 +536,7 @@ class InterfaceClassTests(unittest.TestCase):
self.assertEqual(inst.__name__, 'ITesting')
self.assertEqual(inst.__doc__, '')
self.assertEqual(inst.__bases__, ())
- self.assertEqual(inst.names(), ATTRS.keys())
+ self.assertEqual(list(inst.names()), [])
def test_ctor_attrs_w___annotations__(self):
ATTRS = {'__annotations__': {}}
@@ -545,7 +545,7 @@ class InterfaceClassTests(unittest.TestCase):
self.assertEqual(inst.__name__, 'ITesting')
self.assertEqual(inst.__doc__, '')
self.assertEqual(inst.__bases__, ())
- self.assertEqual(inst.names(), ATTRS.keys())
+ self.assertEqual(list(inst.names()), [])
def test_ctor_attrs_w__decorator_non_return(self):
from zope.interface.interface import _decorator_non_return