technology

The abc of Python

Here’s an example of a python class:

class Greeter(object):
  def greet(self):
    self.say("Hello %s" % self.one_to_greet)

I expect that most people reading this code will immediately determine that this code doesn’t work. However, although it doesn’t do anything on its own it can actually be used as long as code only calls greet on subclasses of Greeter that define say and one_to_greet. This pattern is commonly called abstract class and can be pretty handy. Unfortunately dynamic languages like python don’t support it that well (since it allows you to write them like the very confusing above example). Since the language doesn’t enforce many rules on the code, writers of abstract classes in python have to self-regulate to write code that can be easily understood. Here’s a bit of an improvement:

class AbstractGreeter(object):
"""Abstract class that issues (in some way) a greeting to an entity"""
  def say(self, message):
  """Defines what we should do with a message"""
    raise NotImplementedError
  @property
  def one_to_greet(self):
  """Defines the entity to whom the greeting is addressed"""
    raise NotImplementedError
  def greet(self):
    self.say("Hello %s" % self.one_to_greet)

This cleans up most of the issues that a user of this abstract class would encounter. Now it is clear up-front that this is an abstract class and both the abstract property and method are indicated clearly at the top level of the class rather than simply being referenced somewhere in one of the methods. Furthermore, if an instance does exist and its greet method is called, a NotImplementedError will be raised and the developer will be able to discover the error relatively easily. However, I would prefer if the helpful error occurs earlier than that:

import abc

class AbstractGreeter(object):
  """Abstract class that issues (in some way) a greeting to an entity"""
  __metaclass__ = abc.ABCMeta
  @abc.abstractmethod
  def say(self, message):
    """Defines what we should do with a message"""
    pass
  @abc.abstractproperty
   def one_to_greet(self):
    """Defines the entity to whom the greeting is addressed"""
    pass
  def greet(self):
    self.say("Hello %s" % self.one_to_greet)

abc is a nice package in the python standard library that helps use the abstract class pattern in python projects. With this implementation, code that attempts to use AbstractGreeter without implementing the abstract methods will fail when an instance is created with an exception that lists the unimplemented abstract methods. This is the best way to write abstract classes that I am aware of because it fails the earliest and with the most explicit error message. Unfortunately python by itself will not prevent us from writing code that breaks the abstract class pattern but a good IDE will at least issue a warning if it sees a misuse of an abstract class.

For the sake of completeness I’ll mention the other feature of abc although I don’t actually endorse its use.

import abc
from decimal import Decimal

class Numeric(object):
  __metaclass__ = abc.ABCMeta

Numeric.register(int)
Numeric.register(float)
Numeric.register(complex)
Numeric.register(Decimal)

assert isinstance(0, Numeric)
assert isinstance(0.0, Numeric)
assert isinstance(0j, Numeric)
assert isinstance(Decimal('0'), Numeric)

This code creates a dummy class that is a virtual superclass of standard python numeric types. You could use this virtual superclass to slightly ease type checking when you want to accept any numeric type but I would generally prefer just writing a function is_numeric rather than using the register function.

Of course much of this is a matter of preference and style. If you have different ideas about the use of abstract classes in python, let me know what you think in the comments.

Discussion

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s