Wednesday, April 7, 2010

Python Abstract Class

Many object-oriented programming languages, such as Java, allow the developer to state explicitly that a given class is abstract. Abstract classes cannot be instantiated directly. Instead, only descendants of abstract classes may be created. So how does this benefit the design? The presence of an abstract class means that a specialization must be made because the abstract class is too general to fit any particular purpose. Abstract classes might not provide an implementation of it's operations.

In Python, there is no way to explicitly restrict a class from being instantiated. There are, however, ways to make a Python class implicitly abstract. A common use of abstract classes in Python is to define an interface. For instance, here we define an abstract class and a concrete class.
class AbstractClass(object):

def first(self):
pass

def second(self):
pass

class ConcreteClass(AbstractClass):

def first(self):
print self.__class__.__name__

if __name__ == "__main__":

obj = ConcreteClass()
obj.first()
obj.second()

In this example, AbstractClass defines two methods that aren't implemented. The ConcreteClass class inherits the abstract class but only redefines one method. Yet, we are invoking both methods. So one method does something while the other one does nothing.

This is goo in that the expected interface is supported when the object is put to use, but this approach can lead to bugs that aren't trivial to track down. It isn't at all obvious to the clients of these objects that one of the methods isn't implemented. Consider the following approach.
class AbstractClass(object):

def first(self):
raise NotImplementedError

def second(self):
raise NotImplementedError

class ConcreteClass(AbstractClass):

def first(self):
print self.__class__.__name__

if __name__ == "__main__":

obj = ConcreteClass()

try:
obj.first()
obj.second()
except NotImplementedError:
print "Not implemented. Good to know."

Here, AbstractClass will raise a NotImplementedError instead of doing nothing. This way, it complains to clients of it's instances that it can't do anything with the requested behavior. This certainly makes debugging a lot easier while maintaining the ability to define abstract interfaces for classes.