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