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
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
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.