While at Daniel Lindsley's lecture at Djangocon entitled "API Design Tips", he made a comment about what he considered to be a useless Python base class where all the methods simply raised
NotImplemented
exceptions. While not a literal quote, it looked something like this:
On the surface, I do admit that this class looks pretty useless. It looks like the code a C++ programmer would produce to simulate a virtual base class. A VBC in C++ forces authors of subclasses to create implementations for each of the pure functions in the base class.
A class that follows that perceived intent and implements all the pure methods gains nothing from the base class. Because of Python's "duck typing" abilities, the derived class didn't need the base class at all. It would have just as functional deriving from object as it would from MessageHandlerBase .
I contend that the base class isn't trying to simulate VBCs at all. It is defining optional methods, not required methods. By defining them in the base class as just raising a NotImplemented exception, it is setting up to allow a controlled fallback behavior if a derived class chooses to not implement a method.
Classes A and B can be used with the function notify_of_emergency , but both will fail. Class A raises an AttributeError because of an admittedly intentional problem in the send_message method. Class B raises an AttributeError , too, but for a different reason: it doesn't know how to send a direct message, it can only broadcast. By deriving from MessageHandlerBase , class C is able to implement only the broadcast method. With an instance of class C , the notify_of_emergency function can gracefully degrade.
I think a key point here can be seen in the names of raised exceptions. Classes A and B both raise an error : AttributeError . The term "Error" doesn't appear in the name of the exception NotImplemented , because it is not an error. This exception is used for control flow, not an error condition. I didn't want to use the AttributeError exception for control flow. I don't want to hide legitimate errors by catching them and using them in control flow.
While there are certainly other ways, perhaps even superior, to implement a similar fallback behavior, I believe this example shows that a base class composed entirely of NotImplemented methods can be useful. Even if nothing else, the base class, if commented well to explain the intent, serves as documentation of the methods to be employed by users of the class.
class MessageHandlerBase(object): def send_message(self, *args, **kwargs): raise NotImplemented def broadcast_message(self, *args, **kwargs): raise NotImplemented
On the surface, I do admit that this class looks pretty useless. It looks like the code a C++ programmer would produce to simulate a virtual base class. A VBC in C++ forces authors of subclasses to create implementations for each of the pure functions in the base class.
A class that follows that perceived intent and implements all the pure methods gains nothing from the base class. Because of Python's "duck typing" abilities, the derived class didn't need the base class at all. It would have just as functional deriving from object as it would from MessageHandlerBase .
I contend that the base class isn't trying to simulate VBCs at all. It is defining optional methods, not required methods. By defining them in the base class as just raising a NotImplemented exception, it is setting up to allow a controlled fallback behavior if a derived class chooses to not implement a method.
class A(object): """talk to someone""" def send_message(self, message): person = self.find_someone() person.talk_to_me(message_broken) def broadcast_message(self, message): self.shout(message) class B(object): """send message via radio broadcast""" def broadcast_message(self, message): self.radio.broadcast(message) class C(MessageHandlerBase): """send message by waving flags""" def broadcast_message(self, message): self.flags.wave_message(message) def notify_of_emergency(a_message_handler): """Notify a specific person of an emergency condition. If that is not possible, broadcast the emergency message""" message = "the sky is falling!" try: # try to send the message a_message_handler.send_message(message) except NotImplemented: # fallback to broadcasting the message a_message_handler.broadcast_message(message)
Classes A and B can be used with the function notify_of_emergency , but both will fail. Class A raises an AttributeError because of an admittedly intentional problem in the send_message method. Class B raises an AttributeError , too, but for a different reason: it doesn't know how to send a direct message, it can only broadcast. By deriving from MessageHandlerBase , class C is able to implement only the broadcast method. With an instance of class C , the notify_of_emergency function can gracefully degrade.
I think a key point here can be seen in the names of raised exceptions. Classes A and B both raise an error : AttributeError . The term "Error" doesn't appear in the name of the exception NotImplemented , because it is not an error. This exception is used for control flow, not an error condition. I didn't want to use the AttributeError exception for control flow. I don't want to hide legitimate errors by catching them and using them in control flow.
While there are certainly other ways, perhaps even superior, to implement a similar fallback behavior, I believe this example shows that a base class composed entirely of NotImplemented methods can be useful. Even if nothing else, the base class, if commented well to explain the intent, serves as documentation of the methods to be employed by users of the class.