interface

interface is a library for declaring interfaces and for statically asserting that classes implement those interfaces. It supports Python 2.7 and Python 3.4+.

interface differs from Python’s abc module in two important ways:

  1. Interface requirements are checked at class creation time, rather than at instance creation time. This means that interface can tell you if a class fails to implement an interface even if you never create any instances of that class.

  2. interface requires that method signatures of implementations are compatible with signatures declared in interfaces. For example, the following code using abc does not produce an error:

    >>> from abc import ABCMeta, abstractmethod
    >>> class Base(metaclass=ABCMeta):
    ...     @abstractmethod
    ...     def method(self, a, b):
    ...         pass
    ...
    >>> class Implementation(MyABC):
    ...     def method(self):
    ...         return "This shouldn't work."
    ...
    >>> impl = Implementation()
    >>>
    

    The equivalent code using interface produces an error, telling us that the implementation method doesn’t match the interface:

    >>> from interface import implements, Interface
    >>> class I(Interface):
    ...     def method(self, a, b):
    ...         pass
    ...
    >>> class C(implements(I)):
    ...     def method(self):
    ...         return "This shouldn't work"
    ...
    TypeError:
    class C failed to implement interface I:
    
    The following methods were implemented but had invalid signatures:
      - method(self) != method(self, a, b)
    

Why interface

What is an Interface?

In software generally, an interface is a description of the capabilities provided by a unit of code. In object-oriented languages like Python, interfaces are often defined by lists of method signatures which must be provided by a class.

In interface, an interface is a subclass of interface.Interface that defines a list of methods with empty bodies. For example, the interface definition for a simple Key-Value Store might look like this:

class KeyValueStore(interface.Interface):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

Why Are Interfaces Useful?

Interfaces are useful for specifying the contract between two units of code. By marking that a type implements an interface, we give a programatically-verifiable guarantee that the implementation provides the methods specified by the interface signature. That guarantee makes it easier to write code that can work with any implementation of an interface, and it also serves as a form of documentation.

Usage

Declaring Interfaces

An interface describes a collection of metrhods and properties that should be provided by implementors.

We implement an interface by subclassing from interface.Interface and defining stubs for methods that should be part of the interface:

class MyInterface(interface.Interface):

    def method1(self, x, y, z):
        pass

    def method2(self):
        pass

Implementing Interfaces

To declare that a class implements an interface I, that class should subclass from implements(I):

class MyClass(interface.implements(MyInterface)):

    def method1(self, x, y, z):
        return x + y + z

    def method2(self):
        return "foo"

A class can implement more than one interface:

class MathStudent(Interface):

    def argue(self, topic):
        pass

    def calculate(self, x, y):
        pass


class PhilosophyStudent(Interface):

    def argue(self, topic):
        pass

    def pontificate(self):
        pass


class LiberalArtsStudent(implements(MathStudent, PhilosophyStudent)):

    def argue(self, topic):
        print(topic, "is", ["good", "bad"][random.choice([0, 1])])

    def calculate(self, x, y):
        return x + y

    def pontificate(self):
        print("I think what Wittgenstein was **really** saying is...")

Notice that interfaces can have intersecting methods as long as their signatures match.

Properties

Interfaces can declare non-method attributes that should be provided by implementations using property:

class MyInterface(interface.Interface):

    @property
    def my_property(self):
        pass

Implementations are required to provide a property with the same name.

class MyClass(interface.implements(MyInterface)):

    @property
    def my_property(self):
        return 3

Default Implementations

Sometimes we have a method that should be part of an interface, but which can be implemented in terms of other interface methods. When this happens, you can use interface.default to provide a default implementation of a method.

class ReadOnlyMapping(interface.Interface):

    def get(self, key):
        pass

    def keys(self):
        pass

    @interface.default
    def get_all(self):
        out = {}
        for k in self.keys():
            out[k] = self.get(k)
        return out

Implementors are not required to implement methods with defaults:

class MyReadOnlyMapping(interface.implements(ReadOnlyMapping)):
    def __init__(self, data):
        self._data = data

    def get(self, key):
        return self._data[key]

    def keys(self):
        return self._data.keys()

    # get_all(self) will automatically be copied from the interface default.

Default implementations should always be implemented in terms of other interface methods.

In Python 3, default will show a warning if a default implementation uses non-interface members of an object:

class ReadOnlyMapping(interface.Interface):

    def get(self, key):
        pass

    def keys(self):
        pass

    @interface.default
    def get_all(self):
        # This is supposed to be a default implementation for **any**
        # ReadOnlyMapping, but this implementation assumes that 'self' has
        # an _data attribute that isn't part of the interface!
        return self._data.keys()

Running the above example displays a warning about the default implementation of get_all:

$ python example.py
example.py:4: UnsafeDefault: Default for ReadOnlyMapping.get_all uses non-interface attributes.

The following attributes are used but are not part of the interface:
  - _data

Consider changing ReadOnlyMapping.get_all or making these attributes part of ReadOnlyMapping.
   class ReadOnlyMapping(interface.Interface):

Example

Suppose that we’re writing an application that needs to load and save user preferences.

We expect that we may want to manage preferences differently in different contexts, so we separate out our preference-management code into its own class, and our main application looks like this:

class MyApplication:

    def __init__(self, preferences):
        self.preferences = preferences

    def save_resolution(self, resolution):
        self.preferences.set('resolution', resolution)

    def get_resolution(self):
        return self.preferences.get('resolution')

    def save_volume(self, volume):
        self.preferences.set('volume', volume)

    def get_volume(self):
        return self.preferences.get('volume')

    ...

When we ship our application to users, we store preferences as a json file on the local hard drive:

class JSONFileStore:

    def __init__(self, path):
        self.path = path

    def get(self, key):
        with open(self.path) as f:
            return json.load(f)[key]

    def set(self, key, value):
        with open(self.path) as f:
            data = json.load(f)

        data[key] = value

        with open(self.path, 'w') as f:
            json.dump(data, f)

In testing, however, we want to use a simpler key-value store that stores preferences in memory:

class InMemoryStore:

    def __init__(self):
        self.data = {}

    def get(self, key):
        return self.data[key]

    def set(self, key, value):
        self.data[key] = value

Later on, we add a cloud-sync feature to our application, so we add a third implementation that stores user preferences in a database:

class SQLStore:
    def __init__(self, user, connection):
        self.user = user
        self.connection = connection

    def get(self, key):
        self.connection.execute(
            "SELECT * FROM preferences where key=%s and user=%s",
            [self.key, self.user],
        )

    def set(self, key, value):
        self.connection.execute(
            "INSERT INTO preferences VALUES (%s, %s, %s)",
            [self.user, self.key, value],
)

As the number of KeyValueStore implementations grows, it becomes more and more difficult for us to make changes to our application. If we add a new method to any of the key-value stores, we can’t use it in the application unless we add the same method to the other implementations, but in a large codebase we might not even know what other implementations exist!

By declaring KeyValueStore as an Interface we can get interface to help us keep our implementations in sync:

class KeyValueStore(interface.Interface):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

class JSONFileStore(implements(KeyValueStore)):
    ...

class InMemoryStore(implements(KeyValueStore)):
    ...

class SQLStore(implements(KeyValueStore)):
    ...

Now, if we add a method to the interface without adding it to an implementation, we’ll get a helpful error at class definition time.

For example, if we add a get_default method to the interface but forget to add it to SQLStore:

class KeyValueStore(interface.Interface):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

    def get_default(self, key, default):
        pass


class SQLStore(interface.implements(KeyValueStore)):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

    # We forgot to define get_default!

We get the following error at import time:

$ python example.py
Traceback (most recent call last):
  File "example.py", line 16, in <module>
    class SQLStore(interface.implements(KeyValueStore)):
  File "/home/ssanderson/projects/interface/interface/interface.py", line 394, in __new__
    raise errors[0]
  File "/home/ssanderson/projects/interface/interface/interface.py", line 376, in __new__
    defaults_from_iface = iface.verify(newtype)
  File "/home/ssanderson/projects/interface/interface/interface.py", line 191, in verify
    raise self._invalid_implementation(type_, missing, mistyped, mismatched)
interface.interface.InvalidImplementation:
class SQLStore failed to implement interface KeyValueStore:

The following methods of KeyValueStore were not implemented:
  - get_default(self, key, default)

API Reference

class interface.Interface

Base class for interface definitions.

An interface defines a set of methods and/or attributes that should be provided by one or more classes, which are called “implementations” of the interface.

Classes can declare that they implement an interface I by subclassing implements(I). During class construction, the base class generated by implements(I) will verify that the new class correctly implements all the methods required by I.

Interfaces cannot be instantiated.

Examples

Defining an Interface:

class KeyValueStore(Interface):

    def get(self, key):
        pass

    def set(self, key, value):
        pass

    def delete(self, key):
        pass

Implementing an Interface:

class InMemoryKeyValueStore(implements(KeyValueStore)):

    def __init__(self):
        self.data = {}

    def get(self, key):
        return self.data[key]

    def set(self, key, value):
        self.data[key] = value

    def delete(self, key):
        del self.data[key]

See also

implements()

classmethod from_class(existing_class, subset=None, name=None)

Create an interface from an existing class.

Parameters:
  • existing_class (type) – The type from which to extract an interface.
  • subset (list[str], optional) – List of methods that should be included in the interface. Default is to use all attributes not defined in an empty class.
  • name (str, optional) – Name of the generated interface. Default is existing_class.__name__ + 'Interface'.
Returns:

interface – A new interface class with stubs generated from existing_class.

Return type:

type

interface.implements(*interfaces)

Make a base for a class that implements one or more interfaces.

Parameters:*interfaces (tuple) – One or more subclasses of Interface.
Returns:base – A type validating that subclasses must implement all interface methods of types in interfaces.
Return type:type

Examples

Implementing an Interface:

class MyInterface(Interface):
     def method1(self, x):
         pass

     def method2(self, y):
         pass

class MyImplementation(implements(MyInterface)):
     def method1(self, x):
         return x + 1

     def method2(self, y):
         return y * 2

Implementing Multiple Interfaces:

class I1(Interface):
    def method1(self, x):
        pass

class I2(Interface):
    def method2(self, y):
        pass

class ImplBoth(implements(I1, I2)):
    def method1(self, x):
        return x + 1

    def method2(self, y):
        return y * 2
class interface.default(implementation)

Default implementation of a function in terms of interface methods.

Indices and tables