"Noam Raphael" <[EMAIL PROTECTED]> wrote: > Thanks for your suggestion, but it has several problems which the added > class solves: > > * This is a very long code just to write "you must implement this > method". Having a standard way to say that is better. > * You can instantiate the base class, which doesn't make sense. > * You must use testing to check whether a concrete class which you > derived from the base class really implemented all the abstract methods. > Testing is a good thing, but it seems to me that when the code specifies > exactly what should happen, and it doesn't make sense for it not to > happen, there's no point in having a separate test for it.
Here's a more refined implementation of the one posted before that addresses these issues. It defines 'abstractclass', which would be a nice class decoraror when (and if) class decorators make it into the language. The extra benefit compared to the MustImplement metaclass is that it allows a class with no abstract methods to be defined as abstract. This makes no sense if one defines an abstract class as "a class with one or more abstract (deferred) methods". Perhaps a more useful and general definition of an abstract class is "a class that is not allowed to be instantiated". Deferred methods are one reason for making a class uninstantiable, but it's not the only one. Sometimes it is useful to define a base class that provides a default implementation of a full protocol, yet disallow its direct instantiation, as in the example below. George #=========================================================== # test_abstract.py import unittest from abstract import abstractclass class AbstractTestCase(unittest.TestCase): def test_no_abstractmethods(self): class SomeBase(object): # This class has no abstract methods; yet calling foo() or bar() # on an instance of this class would cause infinite recursion. # Hence it is defined as abstract, and its concrete subclasses # should override at least one of (foo,bar) in a way that breaks # the recursion. def __init__(self, x): self._x = x def foo(self, y): return self.bar(self._x + y) def bar(self, y): return self.foo(self._x - y) SomeBase = abstractclass(SomeBase) class Derived(SomeBase): def __init__(self,x): SomeBase.__init__(self,x) def foo(self,y): return self._x * y self.assertRaises(NotImplementedError, SomeBase, 5) self.assertEquals(Derived(5).bar(2), 15) if __name__ == '__main__': unittest.main() #=========================================================== # abstract.py import inspect __all__ = ["abstractclass", "abstractmethod", "AbstractCheckMeta"] def abstractclass(cls): '''Make a class abstract. Example:: # hopefully class decorators will be supported in python 2.x # for some x, x>4 [EMAIL PROTECTED] class SomeBase(object): @abstractmethod def function(self): """Implement me""" # the only way as of python 2.4 SomeBase = abstractclass(SomeBase) @param cls: A new-style class object. @return: A surrogate of C{cls} that behaves as abstract. The returned class raises NotImplementedError if attempted to be instantiated directly; still its subclasses may call its __init__. A subclass of the returned class is also abstract if it has one or more abstract methods, or if it is also explicitly decorated by this function. A method is declared abstract by being assigned to NotImplemented (or decorated by L{abstractmethod}). @raise TypeError: If there is a metaclass conflict between C{type(cls)} and L{AbstractCheckMeta}, or if C{cls} has an C{__abstractmethods__} attribute. ''' # check if cls has AbstractCheckMeta (or a subtype) for metaclass metaclass = type(cls) if not issubclass(metaclass, AbstractCheckMeta): # it doesn't; try to make AbstractCheckMeta its metaclass by # inheriting from _AbstractCheck cls = metaclass(cls.__name__, (_AbstractCheck,) + cls.__bases__, dict(cls.__dict__)) # replace __init__ with a proxy ensuring that __init__ is called by a # subclass (but not directly) old_init = getattr(cls,'__init__',None) def new_init(self,*args,**kwds): if self.__class__ is cls: raise NotImplementedError("%s is an abstract class" % cls.__name__) if old_init is not None: old_init(self,*args,**kwds) setattr(cls,'__init__',new_init) return cls def abstractmethod(function): '''A method decorator for those who prefer the parameters declared.''' return NotImplemented class AbstractCheckMeta(type): '''A metaclass to detect instantiation of abstract classes.''' def __init__(cls, name, bases, dict): if '__abstractmethods__' in cls.__dict__: raise TypeError("'__abstractmethods__' is already defined in " "class '%s': %s" % (cls.__name__, cls.__dict__['__abstractmethods__'])) type.__init__(cls, name, bases, dict) cls.__abstractmethods__ = [name for name, value in inspect.getmembers(cls) if value is NotImplemented] def __call__(cls, *args, **kwargs): if cls.__abstractmethods__: raise NotImplementedError( "Class '%s' cannot be instantiated: Methods %s are abstract." % (cls.__name__,", ".join(map(repr,cls.__abstractmethods__)))) return type.__call__(cls, *args, **kwargs) class _AbstractCheck(object): ''' A class to stick anywhere in an inheritance chain to make its descendants being checked for whether they are abstract. ''' __metaclass__ = AbstractCheckMeta -- http://mail.python.org/mailman/listinfo/python-list