Entries Tagged as ''

Abstract methods in python (3)

Ok, here is the same code as in the last two articles, this time with more explanation. The point is that python doesn’t have a notion of “abstract methods.” Abstract methods are part of a base class that defines an interface, without any code. Abstract methods can’t be called directly, because they don’t contain any code in their definition.

In the definition of the base class, you may want to include a specific method that is part of the interface, but the specific implementation is still unknown. A popular example seems to be the drawing of a point or a line in a graphical application.

The classes Point and Line share several implementation details, but differ on other. In particular, the way they are drawn is completely different (you will want to optimize the drawing of a line). Suppose these two classes are derived from the same class, Object. It is possible to separate the implementation of the method draw of these two classes, while draw can still be called from the base class Object.

The text below will introduce some utility classes that make this possible.

The goal of this article is defining a way to make it possible to define classes such as the following (not yet paying attention to the proper syntax):

class Object (object):
abstract draw()

def update(self):
self.draw()

class Point (Object):
def draw(self):
...
...

class Line (Object):
def draw(self):
...
...

The method draw of the class Object cannot be implemented, because the concept of
‘drawing’ a generic object is undefined. Other methods, such as the update above may want to use draw anyway, because it is part of the specification for the Object class and its descendants.

The implementation in python exists of two parts:

  1. The definition of a way to declare abstract methods, and
  2. a way to restrict the creation/usage of these abstract classes.

First the declaration part. To declare an abstract method, we can use callable class variables:

class Object (object):
draw = AbstractMethod()

When somebody tries to call Object.draw(), an exception will be raised. But as long as methods in Object use self.draw(), they will actually use Point.draw(), because self
will be of type Point.

If AbstractMethod is a class, draw will be an instance of this class, so we can make draw callable, and raise a proper exception (TypeError or NotImplementedError for example) if it is called instead of an implementation in one of the descendant classes.

class AbstractMethod (object):
def __init__(self, func):
self._function = func

def __get__(self, obj, type):
return self.AbstractMethodHelper(self._function, type)

class AbstractMethodHelper (object):
def __init__(self, func, cls):
self._function = func
self._class = cls

def __call__(self, *args, **kwargs):
raise TypeError('Abstract method `' + self._class.__name__
+ '.' + self._function + '' called')

So now we can declare Object as follows:

class Object (object):
draw = AbstractMethod('draw')
def update(self):
self.draw()

If we tried to call Object().draw() directly, we get an exception:

>>> Object().draw()
TypeError: Abstract method `Object.draw' called

The same happens with Object().update():

>>> Object().update()
TypeError: Abstract method `Object.draw' called

If we implement a descendant class which implements draw, there is no error.

class Point (Object):
def draw(self):
print 'Point.draw called'

(Note that there is no definition for update in Point, it uses the implementation inherited from Object.)

>>> Point().update()
Point.draw called

Of course, we shouldn’t be getting an exception at all if we try to call an abstract function. It should be impossible to create an instance of a class that has one or more abstract methods in its definition (either declared directly in the class definition, or implicitly via inheritance without overriding it with a real method). We can solve this pretty easily by declaring a metaclass that checks if there are any abstract methods in a class definition, and raise an exception if there are.

class Metaclass (type):
def __init__(cls, name, bases, *args, **kwargs):
type.__init__(cls, name, bases, *args, **kwargs)
cls.__new__ = staticmethod(cls.new)

ancestors = list(cls.__mro__)
ancestors.reverse()  # Start with __builtin__.object         for ancestor in ancestors:
for clsname, clst in ancestor.__dict__.items():
if isinstance(clst, AbstractMethod):
abstractmethods.append(clsname)
else:
if clsname in abstractmethods:
abstractmethods.remove(clsname)

abstractmethods.sort()
setattr(cls, '__abstractmethods__', abstractmethods)

def new(self, cls):
if len(cls.__abstractmethods__):
raise NotImplementedError('Can't instantiate class `' +
cls.__name__ + '';n' +
'Abstract methods: ' +
", ".join(cls.__abstractmethods__))

return object.__new__(self)

The definition of Object becomes:

class Object (object):
__metaclass__ = Metaclass
draw = AbstractMethod('draw')

This has the final result:

>>> Point().update()
Point.draw called
>>> Object().update()
NotImplementedError: Can't instantiate class `Object';
Abstract methods: draw

The error can be caught much earlier on when the exception is raised when the class is instantiated.

There is one remaining issue, which is that descendant classes of Object which don’t implement all the abstract methods defined in Object can also not be instantiated:

>>> class FooClass (Object):
...     pass
>>> FooClass()
NotImplementedError: Can't instantiate class `FooClass';
Abstract methods: draw

The code in the last article didn’t do this, but the code in this article checks all ancestors for any abstract methods that haven’t been implemented.