469,342 Members | 5,572 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,342 developers. It's quick & easy.

How about "pure virtual methods"?

Hello,

I thought about a new Python feature. Please tell me what you think
about it.

Say you want to write a base class with some unimplemented methods, that
subclasses must implement (or maybe even just declare an interface, with
no methods implemented). Right now, you don't really have a way to do
it. You can leave the methods with a "pass", or raise a
NotImplementedError, but even in the best solution that I know of,
there's now way to check if a subclass has implemented all the required
methods without running it and testing if it works. Another problem with
the existing solutions is that raising NotImplementedError usually means
"This method might be implemented some time", and not "you must
implement this method when you subclass me".

What I suggest is a new class, called notimplemented (you may suggest a
better name). It would get a function in its constructor, and would just
save a reference to it. The trick is that when a new type (a subclass of
the default type object) is created, It will go over all its members and
check to see if any of them is a notimplemented instance. If that is the
case, it would not allow an instantiation of itself.

What I want is that if I have this module:

======================

class BaseClass(object):
def __init__(self):
...

@notimplemented
def save_data(self, filename):
"""This method should save the internal state of the class to
a file named filename.
"""
pass

class RealClass(BaseClass):
def save_data(self, filename):
open(filename).write(self.data)

======================

then if I try to instantiate BaseClass I would get an exception, but
instantiating RealClass will be ok.
Well, what do you say?

Noam Raphael
Jul 18 '05
51 5943
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.

About the possibility of implementing only a subset of the interface:
You are perfectly welcomed to implement any part of the interface as you
like. Function which use only what you've implemented should work fine
with your classes. But you can't claim your classes to be instances of
the base class - as I see it, subclasses, even in Python, guarantee to
behave like their base classes.

Have a good day,
Noam
Jul 18 '05 #51
"Noam Raphael" <no***@remove.the.dot.myrea.lbox.com> 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
#@abstractclass
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
Jul 18 '05 #52

This discussion thread is closed

Replies have been disabled for this discussion.

By using this site, you agree to our Privacy Policy and Terms of Use.