========================== Interface Specifications ========================== .. currentmodule:: zope.interface.interfaces This document discusses the actual interface objects themselves. We begin with a basic concept of specifying an object's behaviour (with an `ISpecification`), and then we describe the way we write such a specification (`IInterface`). Combinations of specifications (e.g., an object that provides multiple interfaces) are covered by `IDeclaration`. Specification ============= Specification objects implement the API defined by :class:`ISpecification`: .. autointerface:: ISpecification :members: :member-order: bysource .. autoclass:: zope.interface.interface.Specification :no-members: For example: .. doctest:: >>> from zope.interface.interface import Specification >>> from zope.interface import Interface >>> class I1(Interface): ... pass >>> class I2(I1): ... pass >>> class I3(I2): ... pass >>> [i.__name__ for i in I1.__bases__] ['Interface'] >>> [i.__name__ for i in I2.__bases__] ['I1'] >>> I3.extends(I1) True >>> I2.__bases__ = (Interface, ) >>> [i.__name__ for i in I2.__bases__] ['Interface'] >>> I3.extends(I1) False Exmples for :meth:`.Specification.providedBy`: .. doctest:: >>> from zope.interface import * >>> class I1(Interface): ... pass >>> @implementer(I1) ... class C(object): ... pass >>> c = C() >>> class X(object): ... pass >>> x = X() >>> I1.providedBy(x) False >>> I1.providedBy(C) False >>> I1.providedBy(c) True >>> directlyProvides(x, I1) >>> I1.providedBy(x) True >>> directlyProvides(C, I1) >>> I1.providedBy(C) True Examples for :meth:`.Specification.isOrExtends`: .. doctest:: >>> from zope.interface import Interface >>> from zope.interface.declarations import Declaration >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration() >>> int(spec.extends(Interface)) 1 >>> spec = Declaration(I2) >>> int(spec.extends(Interface)) 1 >>> int(spec.extends(I1)) 1 >>> int(spec.extends(I2)) 1 >>> int(spec.extends(I3)) 0 >>> int(spec.extends(I4)) 0 Examples for :meth:`.Specification.interfaces`: .. doctest:: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Specification((I2, I3)) >>> spec = Specification((I4, spec)) >>> i = spec.interfaces() >>> [x.getName() for x in i] ['I4', 'I2', 'I3'] >>> list(i) [] Exmples for :meth:`.Specification.extends`: .. doctest:: >>> from zope.interface import Interface >>> from zope.interface.declarations import Declaration >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration() >>> int(spec.extends(Interface)) 1 >>> spec = Declaration(I2) >>> int(spec.extends(Interface)) 1 >>> int(spec.extends(I1)) 1 >>> int(spec.extends(I2)) 1 >>> int(spec.extends(I3)) 0 >>> int(spec.extends(I4)) 0 >>> I2.extends(I2) False >>> I2.extends(I2, False) True >>> I2.extends(I2, strict=False) True .. _spec_eq_hash: Equality, Hashing, and Comparisons ---------------------------------- Specifications (including their notable subclass `Interface`), are hashed and compared (sorted) based solely on their ``__name__`` and ``__module__``, not including any information about their enclosing scope, if any (e.g., their ``__qualname__``). This means that any two objects created with the same name and module are considered equal and map to the same value in a dictionary. .. doctest:: >>> from zope.interface import Interface >>> class I1(Interface): pass >>> orig_I1 = I1 >>> class I1(Interface): pass >>> I1 is orig_I1 False >>> I1 == orig_I1 True >>> d = {I1: 42} >>> d[orig_I1] 42 >>> def make_nested(): ... class I1(Interface): pass ... return I1 >>> nested_I1 = make_nested() >>> I1 == orig_I1 == nested_I1 True Caveats ~~~~~~~ While this behaviour works well with :ref:`pickling (persistence) `, it has some potential downsides to be aware of. .. rubric:: Weak References The first downside involves weak references. Because weak references hash the same as their underlying object, this can lead to surprising results when weak references are involved, especially if there are cycles involved or if the garbage collector is not based on reference counting (e.g., PyPy). For example, if you redefine an interface named the same as an interface being used in a ``WeakKeyDictionary``, you can get a ``KeyError``, even if you put the new interface into the dictionary. .. doctest:: >>> from zope.interface import Interface >>> import gc >>> from weakref import WeakKeyDictionary >>> wr_dict = WeakKeyDictionary() >>> class I1(Interface): pass >>> wr_dict[I1] = 42 >>> orig_I1 = I1 # Make sure it stays alive >>> class I1(Interface): pass >>> wr_dict[I1] = 2020 >>> del orig_I1 >>> _ = gc.collect() # Sometime later, gc runs and makes sure the original is gone >>> wr_dict[I1] # Cleaning up the original weakref removed the new one Traceback (most recent call last): ... KeyError: ... This is mostly likely a problem in test cases where it is tempting to use the same named interfaces in different test methods. If references to them escape, especially if they are used as the bases of other interfaces, you may find surprising ``KeyError`` exceptions. For this reason, it is best to use distinct names for local interfaces within the same test module. .. rubric:: Providing Dynamic Interfaces If you return an interface created inside a function or method, or otherwise let it escape outside the bounds of that function (such as by having an object provide it), it's important to be aware that it will compare and hash equal to *any* other interface defined in that same module with the same name. This includes interface objects created by other invocations of that function. This can lead to surprising results when querying against those interfaces. We can demonstrate by creating a module-level interface with a common name, and checking that it is provided by an object: .. doctest:: >>> from zope.interface import Interface, alsoProvides, providedBy >>> class ICommon(Interface): ... pass >>> class Obj(object): ... pass >>> obj = Obj() >>> alsoProvides(obj, ICommon) >>> len(list(providedBy(obj))) 1 >>> ICommon.providedBy(obj) True Next, in the same module, we will define a function that dynamically creates an interface of the same name and adds it to an object. .. doctest:: >>> def add_interfaces(obj): ... class ICommon(Interface): ... pass ... class I2(Interface): ... pass ... alsoProvides(obj, ICommon, I2) ... return ICommon ... >>> dynamic_ICommon = add_interfaces(obj) The two instances are *not* identical, but they are equal, and *obj* provides them both: .. doctest:: >>> ICommon is dynamic_ICommon False >>> ICommon == dynamic_ICommon True >>> ICommon.providedBy(obj) True >>> dynamic_ICommon.providedBy(obj) True At this point, we've effectively called ``alsoProvides(obj, ICommon, dynamic_ICommon, I2)``, where the last two interfaces were locally defined in the function. So checking how many interfaces *obj* now provides should return three, right? .. doctest:: >>> len(list(providedBy(obj))) 2 Because ``ICommon == dynamic_ICommon`` due to having the same ``__name__`` and ``__module__``, only one of them is actually provided by the object, for a total of two provided interfaces. (Exactly which one is undefined.) Likewise, if we run the same function again, *obj* will still only provide two interfaces .. doctest:: >>> _ = add_interfaces(obj) >>> len(list(providedBy(obj))) 2 Interface ========= Interfaces are a particular type of `ISpecification` and implement the API defined by :class:`IInterface`. Before we get there, we need to discuss two related concepts. The first is that of an "element", which provides us a simple way to query for information generically (this is important because we'll see that ``IInterface`` implements this interface): .. IElement defines __doc__ to be an Attribute, so the docstring in the class isn't used._ .. autointerface:: IElement Objects that have basic documentation and tagged values. Known derivatives include :class:`IAttribute` and its derivative :class:`IMethod`; these have no notion of inheritance. :class:`IInterface` is also a derivative, and it does have a notion of inheritance, expressed through its ``__bases__`` and ordered in its ``__iro__`` (both defined by :class:`ISpecification`). .. autoclass:: zope.interface.interface.Element :no-members: Next, we look at ``IAttribute`` and ``IMethod``. These make up the content, or body, of an ``Interface``. .. autointerface:: zope.interface.interfaces.IAttribute .. autoclass:: zope.interface.interface.Attribute :no-members: .. autointerface:: IMethod .. autoclass:: zope.interface.interface.Method :no-members: Finally we can look at the definition of ``IInterface``. .. autointerface:: IInterface .. autointerface:: zope.interface.Interface Usage ----- Exmples for :meth:`InterfaceClass.extends`: .. doctest:: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> >>> i = I1.interfaces() >>> [x.getName() for x in i] ['I1'] >>> list(i) []