4

What limitations does inheritance of an interface through an abstract base class present that are addressed by zope.interface?

In the following talk given in 2010 by Jeff Rush about interfaces, at 11:50 Jeff argues that with providedby you are free of the inheritance hierarchy. Why is this important?

If I wanted to use an interface and inherit behavior from another base class, Python grants me the opportunity to do so with multiple inheritance. I understand that multiple inheritance should be avoided, and a compositional design to be considered as an alternative, but in this case I'm not inheriting behavior from the abc but rather just an interface, so I'm not really multiple inheriting in the behavioral sense.

I don't understand the inheritance argument..

Thanks in advance for sharing your insight.

migrated from stackoverflow.com Jun 15 '14 at 14:51

This question came from our site for professional and enthusiast programmers.

  • Can you explain what you mean by "If I wanted to use an interface and inherit behavior from another base class, Python grants me the opportunity to do so with multiple inheritance."? Are you referring to where you do inheritance from multiple classes that aren't child classes of each other? (Eg. Like class Car and class Animal, from which the 2 classes will have no direct relation and justify no inheritance between them, both being used for "class AnimalinCar", which inherits from them both). – Joe Jun 15 '14 at 17:53
  • I implement an interface using as an abstract base class, I subclass the abc. If I want to implement this interface and inherit from another class, I can. – Dowwie Jun 16 '14 at 13:06
  • In an attempt to understand the differences (pros and cons) myself I've created bitbucket.org/prologic/oop_zca_circuits -- I haven't yet formulated any opinions or objective reasonining so right now it's just an implemtnation comparision of three different approaches. – James Mills Jun 17 '14 at 7:40
3

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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.