summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-10-23 15:18:16 -0500
committerJason Madden <jamadden@gmail.com>2020-10-23 15:18:16 -0500
commit27347de8773b42cb3b9c33b2cd95b72b9d31487c (patch)
tree5d3e6309a78f158ef057af6f736ebd5b2040dd55 /docs
parent4f76a54239ee1537040238c5af4a35dd189fbc4c (diff)
downloadzope-interface-27347de8773b42cb3b9c33b2cd95b72b9d31487c.tar.gz
Add example from #220 to the specification docs and expand it.
Diffstat (limited to 'docs')
-rw-r--r--docs/api/specifications.rst105
1 files changed, 97 insertions, 8 deletions
diff --git a/docs/api/specifications.rst b/docs/api/specifications.rst
index 45cf5e1..8a1f21f 100644
--- a/docs/api/specifications.rst
+++ b/docs/api/specifications.rst
@@ -161,11 +161,13 @@ Exmples for :meth:`.Specification.extends`:
>>> I2.extends(I2, strict=False)
True
+.. _spec_eq_hash:
+
Equality, Hashing, and Comparisons
----------------------------------
Specifications (including their notable subclass `Interface`), are
-hashed and compared based solely on their ``__name__`` and
+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
@@ -191,13 +193,22 @@ map to the same value in a dictionary.
>>> I1 == orig_I1 == nested_I1
True
-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.
+Caveats
+~~~~~~~
+
+While this behaviour works will with :ref:`pickling (persistence)
+<global_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::
@@ -225,6 +236,84 @@ 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
=========