diff options
author | Jason Madden <jamadden@gmail.com> | 2020-03-11 12:19:19 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2020-03-18 12:27:20 -0500 |
commit | 413e716f9fb48338829435c4f243133589eb9fe6 (patch) | |
tree | bd00663da33f5b85af67531627f1e39941307fa6 | |
parent | 7afd59d869e3391ce27a4a07ad0931eb5e7910a1 (diff) | |
download | zope-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.py | 50 | ||||
-rw-r--r-- | src/zope/interface/_zope_interface_coptimizations.c | 63 | ||||
-rw-r--r-- | src/zope/interface/declarations.py | 72 | ||||
-rw-r--r-- | src/zope/interface/interface.py | 294 | ||||
-rw-r--r-- | src/zope/interface/tests/test_interface.py | 4 |
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 |