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)