Interfaces (Re: Perl is worse! (was: Python is Wierd!))

Corran Webster cwebster at nevada.edu
Fri Jul 28 16:41:28 EDT 2000


In article <oogg5.2633$6E.681244 at ptah.visi.com>, ge at nowhere.none (Grant 
Edwards) wrote:

> In article <Pine.GSO.4.10.10007281010300.4194-100000 at sundial>, Moshe 
> Zadka wrote:
> >On Fri, 28 Jul 2000, Grant Edwards wrote:
> >
> >> Is 'sequence mapping' for list() an tuple() defined the same as
> >> for the 'for' statement: it has a get_item(i) method that
> >> returns either a value or raises an exception?
> >
> >Actually, I think these two want an __len__ method too.
> 
> At this point somebody is required to complain about the
> vagueness of Python interfaces, and if there was some sort of
> language facility for defining the interface for a "sequence"
> then I wouldn't have had to ask that question.
> 
> Since it's Friday, and the person assigned to the task of
> complaining about interfaces is off today, I've filled in
> temporarily.  But I'm goign golfing at noon, so if you want to
> argue about it you'll have to make an appointment with the
> regular staff on Monday.  Since I'm just filling in, all I'd be
> able to provide is rote contridiction -- which we all know
> isn't a real argument...

Here's a little something that I've been fiddling with in my spare time 
for the past little while.  It's an interface definition mechanism which 
uses metaclasses to allow a heirarchy of interfaces (somewhat like the 
exception heirarchy).  See the docstrings for details on how to use it.

Warning:  This uses metaclasses, so your head may explode if you try to 
follow what is going on here.

Corran


interface.py
----
"""
interface module

Nasty metaclass stuff to define and test interfaces with at least a
little syntactic sugar.

This module defines a class heirarchy:

Interface
    Number
    Stack
    Sequence
        MutableSequence (also sublcass of Stack)
    Mapping
        MutableMapping
    File
        InputFile
        OutputFile
            IOFile
    Iterator

Typically you'd use this something like:
        
    def f(x):
        assert Iterator(x)
        for a in x:
            etc...
    
You can also test classes for compliance with an interface:
        
    class InfiniteIterator:
        def __getitem__(self, n):
            return n
        
    assert Iterator(InfiniteIterator)

This will not work if you expect certain instance variables to be 
present.

To define your own interfaces, simply subclass from one of these and add
your new required methods and instance variables.

To define your own interface, do something like
    
class MyInterface(Interface):
    def __getitem__(self, n):
        pass
    def wibble(self):
        pass
    foo = None
    
Now any class or instance which has both __getitem__ and wibble methods 
and a foo instance or class variable will satisfy the interface.

You can use the __types__ class variable to specify a list of builtin 
types which satisfy the interface.  These are not inherited, so you must 
specify the complete list.

More complex tests can be specified by adding a __test__ method to
the inferface class.  __test__ should return either 0 if the object
does not satisfy the interface, 1 if it does, and 2 if it wants to
default to the usual test.

Interfaces can't have instances, as we are (ab)using the constructor to 
do testing.
"""

from types import *

class InterfaceMetaClass:
    """Metaclass for interfaces
    
    TODO:
        * At the moment it only checks whether methods are callable, not
            whether they have sufficient arguments.  This can be checked
            using introspection on the function objects.
        
        * It would be nice to be able to do pre-condition type checks
            on method arguments something like:
                
                class myiterator:
                    def __getitem__(self, n):
                        if __debug__:
                            Iterator.__getitem__(self, n)
            
            It would be even nicer to do it automagically.
        
        * Fails for new types defined in extensions (can't detect special
            methods).  Users will need to specifically update types.
    """
    
    def __init__(self, name, bases, namespace):
        """Constructor -- create an interface.
        
        This will be called after a class statement where Interface
        is a base class.  By default, instances of any class whose
        namespace includes all the names in namespace (or the namespace
        of a base interface class) will match the interface.
        """
        
        for base in bases:
            if not isinstance(base, InterfaceMetaClass):
                raise TypeError, "Interface base class '%s' must " +\
                        "be an interface"
        bases = filter(lambda x: x is not Interface, bases)
        self.__name__ = name
        self.__bases__ = bases
        self.__types__ = []
        for key in ['__test__', '__doc__', '__types__']:
            try:
                self.__dict__[key] = namespace[key]
            except KeyError:
                pass
            else:
                del namespace[key]
        self.__namespace__ = namespace
        
    def __call__(self, target):
        """Test instance for interface compliance"""
        
        # test for OK python types
        t = type(target)
        if t not in [InstanceType, ClassType]:
            return t in self.__types__
        # test classes and instances
        try:
            if self.__dict__.has_key('__test__'):
                t = self.__test__(target)
                if t == 0 or t == 1:
                    return t
            for (key, value) in self.__namespace__.items():
                if callable(value):
                    if not callable(getattr(target, key)):
                        raise AttributeError
                    # should do some argument checking here to make sure
                    # that our method expects at a minimum no more than
                    # the target method, and at a maximum no less than
                    # the target's maximum
                else:
                    getattr(target, key)
            for base in self.__bases__:
                if not base(target):
                    return 0
        except AttributeError:
            return 0
        return 1    

# Create the base class for interfaces
# It is an empty interface

Interface = InterfaceMetaClass("Interface", (), {})

class Number(Interface):
    __types__ = [IntType, LongType, FloatType, ComplexType]
    def __add__(self, other):
        pass
    
    def __radd__(self, other):
        pass

    def __sub__(self, other):
        pass
    
    def __rsub__(self, other):
        pass

    def __mul__(self, other):
        pass
    
    def __rmul__(self, other):
        pass
        
    def __div__(self, other):
        pass
    
    def __rdiv__(self, other):
        pass
    
    def __neg__(self):
        pass
    
    def __pos__(self):
        pass
    
    # probably should have other stuff like __pow__ and __divmod__


class Sequence(Interface):
    __types__ = [ListType, TupleType, StringType, BufferType]
    def __len__(self):
        """The length of the sequence"""
        pass
    
    def __getitem__(self, n):
        """Return the nth item in the sequence"""
        assert type(n) == IntegerType or type(n) == LongType
    
    def __getslice__(self, i, j):
        """Return the slice from i to j"""
        assert type(i) == IntegerType or type(i) == LongType
        assert type(j) == IntegerType or type(j) == LongType
    
    def __add__(self, other):
        """Add two sequences together"""
        pass
    
    def __radd__(self, other):
        """Add two sequences together"""
        pass
    
    def __mul__(self, n):
        """Repeat the sequence n times"""
        assert type(n) == IntegerType
    
    def __rmul__(self, n):
        """Repeat the sequence n times"""
        assert type(n) == IntegerType or type(n) == LongType


class Mapping(Interface):
    __types__ = [DictType]
    def __len__(self):
        """Return the number of entries in the mapping"""
        pass
    
    def __getitem__(self, key):
        """Return the value corresponding to the key"""
        pass
    
    def has_key(self, key):
        """Return true if the mapping has the key"""
        pass
    
    def keys(self):
        """Return the list of keys"""
        pass
    
    def values(self):
        """Return the values from the mapping"""
        pass
    
    def items(self):
        """Return a list of (key, value) pairs"""
        pass
    
    def get(self, key, value=None):
        """Return the value corresponding to the key if the key exists, 
else return value"""
        pass
    
    def copy(self):
        """Return a copy of the mapping"""
        pass

class Stack(Interface):
    __types__ = [ListType]
    def pop(self):
        """Pop the top element off the stack"""
        pass
    
    def append(self, item):
        """Push an element onto the stack"""
        pass


class MutableSequence(Sequence, Stack):
    __types__ = [ListType]
    def __len__(self):
        """The length of the sequence"""
        pass
    
    def __setitem__(self, n, value):
        """Set the nth item in the sequence to value"""
        assert type(n) == IntegerType or type(n) == LongType
    
    def __setslice__(self, i, j, sequence):
        """Return the slice from i to j"""
        assert type(i) == IntegerType or type(i) == LongType
        assert type(j) == IntegerType or type(j) == LongType
    
    def __delitem__(self, n):
        """Delete the nth item from the sequence"""
        assert type(n) == IntegerType or type(n) == LongType
    
    def __setslice__(self, i, j):
        """Delete the slice from i to j"""
        assert type(i) == IntegerType or type(i) == LongType
        assert type(j) == IntegerType or type(j) == LongType
    
    def count(self, value):
        pass
    
    def index(self, value):
        pass
    
    def insert(self, n, value):
        assert type(n) == IntegerType or type(n) == LongType
    
    def remove(self, value):
        pass
    
    def reverse(self):
        pass
    
    def sort(self):
        pass


class MutableMapping(Mapping):
    __types__ = [DictType]
    def __setitem__(self, key, value):
        """Set the value corresponding to key"""
        pass
    
    def __delitem__(self, key):
        """Delete the value corresponding to key"""
        pass
    
    def clear(self):
        """Remove all items from the mapping"""
        pass
    
    def update(self, m):
        """Add entries from m to the mapping"""
        assert Mapping(m)

class File(Interface):
    __types__ = [FileType]
    
    closed = 0
    
    def close(self):
        pass

class InputFile(File):
    __types__ = [FileType]
    
    def read(self, size=None):
        pass
    
    def readline(self, size=None):
        pass
    
    def readlines(self, sizehint=None):
        pass

class OutputFile(File):
    __types__ = [FileType]
    
    def write(self, size=None):
        pass
    
    def writelines(self, sizehint=None):
        pass

class IOFile(InputFile, OutputFile):
    __types__ = [FileType]


# Additional useful interfaces

class Iterator(Interface):
    """Iterators can take the place of standard sequences in for loops"""
    __types__ = [ListType, TupleType, StringType]

    def __getitem__(self, n):
        """Return the nth item in the sequence or IndexError if done"""
        assert type(n) == IntegerType or type(n) == LongType
        assert n >= 0



More information about the Python-list mailing list