Implementing Interface Contracts in Python With Class Decorators
Python decorators are quite useful and interesting. I’ve already written about function decorators in a previous post and class decorators are a worthy followup.
I order to illustrate this feature, we’ll implement support for interface contracts for Python classes. In a language like Java, for instance, a contract can be declared this way:
public interface Comparable<T> {
public int compareTo(T o);
}
And a class declared as an implementation of this interface must provide the relevant methods (with the correct signature). Otherwise, a compilation error arises. For instance:
public final class Integer extends Number implements Comparable<Integer> {
...
public int compareTo(Integer anotherInteger) {
int thisVal = this.value;
int anotherVal = anotherInteger.value;
return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
}
...
}
We’ll try to achieve a similar feature in Python, albeit with some limitations. First, as Python does not have an explicit compilation step, our errors will only arise in runtime. Second, in Python, the types of the arguments and return values aren’t present in the signature of methods, so we’ll leave it out too. This is a notable absence, but we can still verify the presence of desired methods in the implementer type and the following elements in the method’s signature:
This said, we now have to declare our interfaces. I picked the following (somewhat cumbersome) syntax:
class some_interface(interface):
simple_method = method(['self'])
method_with_args = method(['self', 'arg1', 'arg2'])
method_with_varargs = method(['self'], varargs='varargs')
method_with_kwargs = method(['self'], keywords='kwargs')
method_with_default_argument = method(['self', 'default_arg'], defaults=1)
Here we have an ordinary class subtyping an interface class. The methods are
defined as attributes of the some_interface class, holding instances of the
method class.
The interface class isn’t really exciting or even necessary. Actually, it
exists for the sole purpose of tagging an interface contract as such:
class interface(object):
pass
The method class holds the method signature.
class method(object):
def __init__(self, args=None, varargs=None, keywords=None, defaults=0):
self.args = args or []
self.varargs = varargs
self.keywords = keywords
self.defaults = defaults
Notice that the constructor takes a few arguments:
Now we can create a new type implementing some_interface. And it follows:
@implements(some_interface)
class some_class(object):
def simple_method(self):
pass
def method_with_args(self, arg):
pass
def method_with_varargs(self, *varargs):
pass
def method_with_kwargs(self, **kwargs):
pass
def method_with_default_argument(self, default_arg=100):
pass
Notice that we declare the class as implementing the contract through the
implements class decorator.
First thing we have to observe is the initialization of the implements
decorator. Once the decorated class is loaded by the interpreter, an instance of
the decorator is produced. The argument some_interface is passed to the
constructor during the initialization (and it’s stored as an attribute for
future reference).
class implements(object):
def __init__(self, interface):
self.interface = interface
...
Then, the __call__ method of the decorator is invoked. The decorated class
(some_class in this example) is passed as argument. In this method, it is
verified whether the given class satisfies all of its contractual obligations.
If no problem arises, the class is returned at the end of the call.
class implements(object):
...
def __call__(self, clazz):
methods = [each for each in dir(self.interface) if self._is_method(each)]
for each in methods:
self._assert_implements(clazz, each)
return clazz
...
Notice that the _assert_implements method is called against each method
declared in the interface. This method, listed ahead, verifies whether the given
method, as implemented by the class, has the desired signature, as specified in
the interface. In case it doesn’t, an exception is raised (effectively
interrupting the program).
class implements(object):
...
def _assert_implements(self, clazz, method_name):
method_contract = object.__getattribute__(self.interface, method_name)
method_impl = getargspec(object.__getattribute__(clazz, method_name))
assert method_name in dir(clazz)
assert method_contract.args == method_impl.args
assert method_contract.varargs == method_impl.varargs
assert method_contract.keywords == method_impl.keywords
if (method_impl.defaults is not None):
assert method_contract.defaults == len(method_impl.defaults)
else:
assert method_contract.defaults == 0
...
The object.__getattribute__()
(
documentation) call is one thing worth of note as it is part of Python’s
introspection features and targeted at retrieving attributes from classes and
objects. Here, it’s used both to retrieve method objects from the interface
and and to obtain references to the actual methods in the concrete class (the
clazz argument).
The getargspec function
(
documentation) is also an interesting introspection feature. Given a
function, it will return an object with information about the given function’s
signature.
So, having both the expected signature from the method object hosted in the
interface and the signature of the method implemented in the concrete class, we
can simply compare them both and hope for a match.
(Postscript: although the source code presented above works fine, it’s actually a shorter version of the code originally developed for the purpose and available here.)