The video does not appear to be available as of December 2016, but I agree with you that interface inheritance is not as problematic as implementation inheritance. See https://softwareengineering.stackexchange.com/a/260354 for example.
However, one sense in which the zope.interface
"provider" relationship frees us from the inheritance hierarchy is that it allows objects to provide an interface even if their class does not. https://docs.zope.org/zope.interface/README.html#declaring-provided-interfaces shows examples of objects directly providing an interface although their respective classes do not.
For example, if an IWriter
interface requires write
and flush
methods, then a module that defines top-level write
and flush
functions (and calls zope.interface.moduleProvides(IWriter)
) provides that interface, even though modules in general do not.
This is also the reason that Zope interface methods do not name self
as a parameter - self
is an implementation detail particular to class instance methods.
This ability for individual objects to provide an interface becomes particularly useful with objects that wrap another object and dynamically map its methods.
For example, say we have:
class IWriter(zope.interface.Interface):
def write(s):
"""Write stuff"""
def flush():
"""Flush stuff"""
class TextWriter:
zope.interface.implements(IWriter)
def write(self, s):
...
def flush(self):
...
tw = TextWriter()
do_something(tw)
If we see a problem where tw
output is not being flushed sometimes, we could instrument the TextWriter methods to display debug info (e.g. log(timestamp, caller, methodname)
). However, the code to generate the timestamp
and caller
info is complex, and needs to be added in multiple places. And now every TextWriter
instance will display the debug info.
Instead, we could wrap the particular TextWriter
of interest in a wrapper object that logs its method calls.
class DebugWrapper:
def __init__(self, wrapped):
self._wrapped = wrapped
def __getattr__(self, name):
... # generate timestamp and caller
log(timestamp, caller, name)
return getattr(self._wrapped, name)
dtw = DebugWrapper(tw)
do_something(dtw)
Note that DebugWrapper
is generic. It can wrap any object. It could be imported from a separate library. It's independent of the IWriter
interface, and that is good since we can more easily re-use it elsewhere.
But if the do_something
function expects to receive a provider of the interface IWriter
we now have the problem that dtw
satisfies the interface syntax but does not "officially" provide the interface.
This is an important distinction.
Interfaces do not just define a syntactic signature. They also define the semantic meaning of the syntax. With interfaces we can specify whether two objects that provide the same methods actually provide the same semantics. This is a big difference to Python's common use of duck typing, where the same syntax implies the same semantics.
Anyway, the solution to our problem is to inform everyone that the object dtw
does indeed provide the IWriter
interface, even though its class DebugWrapper
does not.
zope.interface.directlyProvides(dtw, IWriter)