Using zope.interface

There are few frameworks that allow you to build explicit interfaces in Python. The most notable one is a part of the Zope project. It is the zope.interface package. Although nowadays, Zope is not as popular as it used to be, the zope.interface package is still one of the main components of the Twisted framework.

The core class of the zope.interface package is the Interface class. It allows you to explicitly define a new interface by subclassing. Let's assume that we want to define the obligatory interface for every implementation of a rectangle:

from zope.interface import Interface, Attribute 
 
 
class IRectangle(Interface): 
    width = Attribute("The width of rectangle") 
    height = Attribute("The height of rectangle") 
 
    def area(): 
        """ Return area of rectangle 
        """ 
 
    def perimeter(): 
        """ Return perimeter of rectangle 
        """

Here are some important things to remember when defining interfaces with zope.interface:

When you have such a contract defined, you can then define new concrete classes that provide an implementation for our IRectangle interface. In order to do that, you need to use the implementer() class decorator and implement all of the defined methods and attributes:

@implementer(IRectangle) 
class Square: 
    """ Concrete implementation of square with rectangle interface 
    """ 
 
    def __init__(self, size): 
        self.size = size 
 
    @property 
    def width(self): 
        return self.size 
 
    @property 
    def height(self): 
        return self.size 
 
    def area(self): 
        return self.size ** 2 
 
    def perimeter(self): 
        return 4 * self.size 
 
 
@implementer(IRectangle) 
class Rectangle: 
    """ Concrete implementation of rectangle 
    """ 
    def __init__(self, width, height): 
        self.width = width 
        self.height = height 
 
 
    def area(self): 
        return self.width * self.height 
 
    def perimeter(self): 
        return self.width * 2 + self.height * 2 

It is common to say that the interface defines a contract that a concrete implementation needs to fulfil. The main benefit of this design pattern is being able to verify consistency between contract and implementation before the object is being used. With the ordinary duck-typing approach, you only find inconsistencies when there is a missing attribute or method at runtime. With zope.interface, you can introspect the actual implementation using two methods from the zope.interface.verify module to find inconsistencies early on:

Since we have defined our interface and two concrete implementations, let's verify their contracts in an interactive session:

>>> from zope.interface.verify import verifyClass, verifyObject
>>> verifyObject(IRectangle, Square(2))
True
>>> verifyClass(IRectangle, Square)
True
>>> verifyObject(IRectangle, Rectangle(2, 2))
True
>>> verifyClass(IRectangle, Rectangle)
True

This is nothing impressive. The Rectangle and Square classes carefully follow the defined contract, so there is nothing more to see than a successful verification. But what happens when we make a mistake? Let's see an example of two classes that fail to provide full IRectangle interface implementation:

@implementer(IRectangle) 
class Point: 
    def __init__(self, x, y): 
        self.x = x 
        self.y = y 
 
 
@implementer(IRectangle) 
class Circle: 
    def __init__(self, radius): 
        self.radius = radius 
 
    def area(self): 
        return math.pi * self.radius ** 2 
 
    def perimeter(self): 
        return 2 * math.pi * self.radius 

The Point class does not provide any method or attribute of the IRectangle interface, so its verification will show inconsistencies already on the class level:

>>> verifyClass(IRectangle, Point)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "zope/interface/verify.py", line 102, in verifyClass
    return _verify(iface, candidate, tentative, vtype='c')
  File "zope/interface/verify.py", line 62, in _verify
    raise BrokenImplementation(iface, name)
zope.interface.exceptions.BrokenImplementation: An object has failed to implement interface <InterfaceClass __main__.IRectangle>
    
         The perimeter attribute was not provided.

The Circle class is a bit more problematic. It has all the interface methods defined, but breaks the contract on the instance attribute level. This is the reason, in most cases, that you need to use the verifyObject() function to completely verify the interface implementation:

>>> verifyObject(IRectangle, Circle(2))

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "zope/interface/verify.py", line 105, in verifyObject
    return _verify(iface, candidate, tentative, vtype='o')
  File "zope/interface/verify.py", line 62, in _verify
    raise BrokenImplementation(iface, name)
zope.interface.exceptions.BrokenImplementation: An object has failed to implement interface <InterfaceClass __main__.IRectangle>
    
        The width attribute was not provided.

Using zope.inteface is an interesting way to decouple your application. It allows you to enforce proper object interfaces without the need for the overblown complexity of multiple inheritances, and also allows you to catch inconsistencies early. But the biggest downside of this approach is the requirement to explicitly define that the given class follows some interface in order to be verified. This is especially troublesome if you need to verify instances coming from the external classes of built-in libraries. zope.interface provides some solutions for that problem and you can, of course, handle such issues on your own by using the adapter pattern, or even monkey-patching. Anyway, the simplicity of such solutions is at least debatable.