diff options
| author | Jason Madden <jamadden@gmail.com> | 2020-10-23 15:18:16 -0500 |
|---|---|---|
| committer | Jason Madden <jamadden@gmail.com> | 2020-10-23 15:18:16 -0500 |
| commit | 27347de8773b42cb3b9c33b2cd95b72b9d31487c (patch) | |
| tree | 5d3e6309a78f158ef057af6f736ebd5b2040dd55 /docs | |
| parent | 4f76a54239ee1537040238c5af4a35dd189fbc4c (diff) | |
| download | zope-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.rst | 105 |
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 ========= |
